facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.7k stars 46.81k forks source link

[React DevTools] Bug: Cannot update a component from inside the function body of a different component. #18147

Closed lxsmnsyc closed 4 years ago

lxsmnsyc commented 4 years ago

After updating React to 16.13, I am receiving this error, which is pretty cryptic and only occurs on that update (16.12 works fine)

Screen Shot 2020-02-27 at 5 07 33 PM

Demo can be found here: https://github.com/LXSMNSYC/react-scoped-model/tree/master/example

samparnell commented 4 years ago

I have this issue too. @LXSMNSYC what was the solution?

lxsmnsyc commented 4 years ago

@samparnell apparently when I encountered this "problem", I wasn't aware about the changelogs, and so I read it and at the very beginning of the logs: https://reactjs.org/blog/2020/02/26/react-v16.13.0.html, the warning is indicated there.

TL;DR don't synchronously call setState inside other functional components, defer the call to useEffect.

Something like this yields a warning:

function Child({ setState }) {
  setState(true);

  return <h1>This is a child.</h1>;
}

function Parent() {
  const [state, setState] = useState(false);

  return <Child setState={setState} />;
}

so it should be:

function Child({ setState }) {
  useEffect(() => {
    setState(true);
  }, [setState]);
  return <h1>This is a child.</h1>;
}
minirom commented 4 years ago

Yeah I have similar issue on a project

What about a setState used in a callback in a child like :

function Child({ callback }) {
  return <button onClick={callback}>This is a child.</button>;
}

function Parent() {
  const [state, setState] = useState(false);

  const callback = useCallback(() => {
    setState(true);
  }, []);

  return <Child callback={callback} />;
}

Is there a way to avoid raising the warning in this case ?

eps1lon commented 4 years ago

@minirom You're not calling callback during render but in an event handler. This doesn't seem to cause any issues

minirom commented 4 years ago

@eps1lon in my case it does (in my project), so I am a little bit confused.

there are a lot of callback like this, I will try to investigate it more and create a new issue when I understand why I have this warning raised with the callbacks

eps1lon commented 4 years ago

@eps1lon in my case it does (in my project), so I am a little bit confused.

Me as well since the code you posted did not do what you're describing.

minirom commented 4 years ago

Nice one ! with that exchange I found a callback directly called in the component

I did not noticed that before

Thanks ;)

sea-dhassouni commented 4 years ago

I seem to be hitting a similar issue to @minirom - I am trying to do this from a callback and hit this error. I have a component that is my parent, wrapping Antd's Select component. Here is quick code sandbox to illustrate. This seems to be the parent calling a child with an event handler scenario, and it still throws that warning. @eps1lon you seemed to think this shouldn't be an issue, so I am sure I am missing something the way my code is structured. Would love your thoughts. Thanks!

gnoesiboe commented 4 years ago

Is there another way to get rid of this warning when you don't have the possibility of wrapping the trigger in a useEffect? For example I am using an external library that has an onChange callback prop, that I implement and call setState in. This external library calls the onChange somewhere in the render cycle (componentWillMount), which in turn causes the warning in my app. What would be the way to fix this in this external class component or in my application?

lxsmnsyc commented 4 years ago

Is there another way to get rid of this warning when you don't have the possibility of wrapping the trigger in a useEffect? For example I am using an external library that has an onChange callback prop, that I implement and call setState in. This external library calls the onChange somewhere in the render cycle (componentWillMount), which in turn causes the warning in my app. What would be the way to fix this in this external class component or in my application?

The same issue has been submitted to that external library. Await some fix and hope for it to solve your problem 😁 Meanwhile, I think you could defer the setState by means of scheduling i.e. Promise.resolve()

gnoesiboe commented 4 years ago

@LXSMNSYC thanks! I'll give it a try!

gnoesiboe commented 4 years ago

@LXSMNSYC for now I found a workaround by putting my state update in a setTimeout(() => ..), and I'll wait for the fix. Thanks!

penx commented 4 years ago

I get this warning for every useHover hook implementation I can find, including ones that are using useCallback or useEffect - which I understand should avoid this warning:

https://codesandbox.io/s/react-issue-18147-i2lu9

kuangbeibei commented 4 years ago

execute callback , window.addEventListener, history api , etc, inside "useEffect" hook

qklhk commented 4 years ago

那我用ES6类组件写的时候也会出现这个问题,警告和你们说的一模一样,现在问题解决了吗

cyphire commented 4 years ago

If anyone is following this thread, I have had to do this lots of times in React... Using redux, etc. by passing the callback function from the parent...

You don't get the error you are talking about because it's the child which ends up running the function, but it is still encapsulated from the parent.

// PARENT COMPONENT which passes AccountList to Semantic Cards

    const globalData = useSelector(state => state.appdata)
    const dispatch = useDispatch();

    <AccountList 
        accounts={accounts} 

        // THIS IS WHERE I PASS THE CALLBACK FROM THE PARENT (WITH the redux stuff needed to dispatch actions)
        initAccount={(account) => initAccount(globalData, dispatch, Creators, account)
        }
    />

// // AccountList


const mapAccounts = (accounts, initAccount) => {
console.log(initAccount)
    return (
        accounts.map(account => {
            return {
            as: Link,
            to: '/storePage/',

            // This onclick is what creates the route change (and note that it sends in what has changed (the accountID.))
            // further note that in the parent I need the dispatch, Creators, and globalData (my state), but only need to pass the account as they are already in the function
            onClick: () => initAccount(account.accountID),
            state: { accountID: account.accountID },
            childKey: account.accountID,
            header: 'Account: '+account.accountID,
            meta: <Card.Meta style={{color: 'dimgray'}} state={{ accountID: account.accountID, siteTitle: account.siteTitle }}>{12.99}</Card.Meta>,
            }
        })
    )
}

// THIS IS WHERE I SENT THE paramaters in (from the parent... Note it maps through and sends the function to the onClick above...)

export default ({accounts, initAccount}) => (
  <Card.Group items={mapAccounts(accounts, initAccount)} itemsPerRow={1} stackable />
)
dl-husky73 commented 4 years ago

For everyone on this thread, I had this issue and fixed it by following someone's suggestion of adding key to the Picker. That solved the problem for me, all is working great. I used the value from list which is unique. See below: <RNPickerSelect key={gStuffID} onValueChange={value => handleSelectStuff(value)} placeholder={{ label: 'Select Stuff' }} items={gStuffList} style={pickerSelectStyles} pickerProps={styles.rnPickerProps} useNativeAndroidPickerStyle={false} value={gStuffID} />

GrinchakAndrew commented 4 years ago

I have this issue when updating global state through a redux setter-action from function body of one component to subscriber-component on a different screen, to change 2 sibling components on different screens (React Native). And this innovation in React 13 really sucks, as it breaks the whole pattern of updation. React really started to suck, guys. And no matter what I tried to do - useEffect, etc., this problem persists now after upgrade. IMHO, setting globally state through redux for sibling components should not be an issue. Finally, React is shooting itself in the feet... Back to the previous version is the only solution for now. But what is the future of this, anyways?..

lxsmnsyc commented 4 years ago

I have this issue when updating global state through a redux setter-action from function body of one component to subscriber-component on a different screen, to change 2 sibling components on different screens (React Native). And this innovation in React 13 really sucks, as it breaks the whole pattern of updation. React really started to suck, guys. And no matter what I tried to do - useEffect, etc., this problem persists now after upgrade. IMHO, setting globally state through redux for sibling components should not be an issue. Finally, React is shooting itself in the feet... Back to the previous version is the only solution for now. But what is the future of this, anyways?..

You might be confused. This problem only arises in the user-land. The problem does not occur in React, the problem occurs in your implementation.