Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

React 16.13.0 'Warning: Cannot update a component from inside the function body of a different component.' #3863

Closed
bogdibota opened this issue Feb 28, 2020 · 27 comments
Assignees
Labels
bug confirmed has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository in-progress
Projects

Comments

@bogdibota
Copy link

bogdibota commented Feb 28, 2020

Intended outcome:

After updating to React 16.13.0, useQuery should not throw unintentional state changes warning.

Actual outcome:

After updating to React 16.13.0, in dev mode, every component that uses useQuery without skip: true will throw this new warning.

Warning: Cannot update a component from inside the function body of a different component.

image

More info about this warning can be found in the React changelog.

The actual functionality appears to be the same, from what I can tell.

How to reproduce the issue:

Update your React version to 16.13.0 where this warning was introduced and it will appear in the console once for each component using useQuery without skip: true.

Version

  System:
    OS: Windows 10 10.0.18362
  Binaries:
    Node: 11.14.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.19.1 - C:\Program Files\nodejs\yarn.CMD
    npm: 6.7.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: 44.18362.449.0
  npmPackages:
    apollo-cache-inmemory: ^1.6.5 => 1.6.5
    apollo-client: ^2.6.8 => 2.6.8
    apollo-link: ^1.2.13 => 1.2.13
    apollo-link-error: ^1.1.12 => 1.1.12
    apollo-link-http: ^1.5.16 => 1.5.16
    apollo-upload-client: ^12.1.0 => 12.1.0
    apollo-utilities: ^1.3.3 => 1.3.3
    react-apollo-network-status: ^3.0.0 => 3.0.0

Update 05.03:

  • thanks to @dannycochran, we have a narrow reproduction case https://github.com/bogdibota/usequery
  • my idea so far: This seems to be caused by a component A updating the cache and another component B that listens to the same query as component A gets updated during the rendering phase.
  • the reason my initial error indicated that every component throws this warning is that i am using react-apollo-network-status for a global loading indicator. This loading indicator was updated by all the components that had a useQuery hook (as expected)
  • IMPORTANT: the warning's stacktrace is showing the component that initialed the changes, not the one that gets re-renderd by those changes 🤦‍♂
@DamianBusz
Copy link

This issue might help you.

@dannycochran
Copy link

dannycochran commented Mar 5, 2020

@DamianBusz @bogdibota I spent a lot of time trying to create a narrow reproduction case, and did so here:

https://github.com/dannycochran/usequery
EDIT, simpler repro: https://github.com/bogdibota/usequery

That demo does not use "useEffect" or "setState" at all, the only effect it calls is Apollo's "useQuery".

As I mention in the README.md, there are a number of ways to making the warnings disappear, but it doesn't explain why they're happening.

IMO this looks like something within @apollo/react-hooks that needs to be addressed.

@ckruse
Copy link

ckruse commented Mar 5, 2020

Same here. The only effect i am use is useQuery.

@bogdibota
Copy link
Author

@dannycochran thanks for the hard work to reproduce the issue!!

I have forked you repo and managed to isolate the issue a little further: https://github.com/bogdibota/usequery

I have documented in the README more ways to make the warning go away and i've concluded the following: this seems to be caused by a component A updating the cache and another component B that listens to the same query as component A gets updated during the rendering phase.

I think the reason that, in my example, you don't always get an error with 2 components is because, somehow, the query response is received and handled in the 'correct' order.

@dannycochran
Copy link

@bogdibota confirmed I see the error pretty consistently in your repro, thanks for narrowing it down further.

@fev4
Copy link

fev4 commented Mar 10, 2020

Same issue here. Thanks for narrowing it down.

It does seem to be that querying the cache locally is somehow causing this.

For context, I'm writing to the cache a value from inside a children component, and then I'm calling back the cached value (using @client) from the parent with useQuery. When I remove this useQuery from the parent component the issue goes away.

I tried the 4 options in @bogdibota but none of them work so far.

@zhouzi
Copy link

zhouzi commented Mar 11, 2020

I just noticed this error in an application that is not using hooks but the components API. I am not sure if that makes a difference since the components are using the hooks under the hood.

I followed Dan Abramov's recommendation to track down the culprit. The last line being called before handing over to react is this.forceUpdate():

private startQuerySubscription() {
  ...
      // Make sure we're not attempting to re-render similar results
      if (
        previousResult &&
        previousResult.loading === loading &&
        previousResult.networkStatus === networkStatus &&
        isEqual(previousResult.data, data)
      ) {
        return;
      }

      this.forceUpdate();
      ^^^^^^^^^^^^^^^^^^^
    },
    error: error => {
  ...

My guess is that the forceUpdate function passed by useBaseQuery to QueryData is called synchronously when the query's data is already cached. So perhaps the call should be wrapped within a setTimeout(fn, 0) or equivalent. It's just a wild guess though, I'm not familiar with Apollo's code at all.

@klavs
Copy link

klavs commented Mar 12, 2020

An investigation of an issue we have yielded similar results.

That next function is called synchronously if another component has subscribed to that observable. It effectively works as if the code was written like this:

export function useBaseQuery() {
  const [tick, forceUpdate] = useReducer(x => x + 1, 0);
  forceUpdate();

  return ...;
}

I guess the observable is the confusing part, because it can sometimes run next asynchronously and sometimes synchronously.

https://github.com/apollographql/apollo-client/blob/8ec3672ab70b2169d7e072014548fbe890f75b85/src/react/data/QueryData.ts#L271-L276

@austinh
Copy link

austinh commented Mar 19, 2020

I am also getting this error, mainly when I have two components, both of which execute the same query, and one comes back before the other.

@hwillson hwillson self-assigned this Mar 21, 2020
@hwillson hwillson added bug confirmed has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository in-progress labels Mar 22, 2020
@hwillson hwillson added this to Web in Client Team Mar 23, 2020
@pawelkleczek
Copy link
Contributor

in 16.13.1 seems it has become smth like:

index.js:1 Warning: Cannot update a component (`InputProxyBuy`) while rendering a different component (`AdditionalInfo`). To locate the bad setState() call inside `AdditionalInfo`, follow the stack trace as described in https://fb.me/setstate-in-render

both components make use of the same useQuery call to fetch local state

@hwillson
Copy link
Member

@pawelkleczek we're working on this.

@bzhr
Copy link

bzhr commented Mar 28, 2020

For me this happens when I pass refetch from parent to child. Reading from the issue on React, it's suggested to wrap the call in useEffect in the child component and set the state in there, however this also throws the same error.

Example code:

const Parent = ({data, refetch}) => {
  const [shouldRefetchTable, updateRefetchTable] = useState(false);
  if (shouldRefetchTable) {
    refetch()
  }
  return <Child shouldRefetch={shouldRefetchTable} />}
const Child = ({shouldRefetchTable}) => {
  const onClick = () =>
    useEffect(() => {
      shouldRefetchTable(true)
     })
  return <button onClick={onClick}>Refetch Data</button>
}

So, for a lack of a solution for refetching, I just call client.resetStore()

@andrewgreenh
Copy link

@bzhr the problem is, that you are calling refetch during render (of Parent). As you can imagine, refetch could set a bunch of states in different components at different levels (all components that subscribe to this query) so this must not be called during render. Those side effects were always supposed to be called in a useEffect();

@bzhr
Copy link

bzhr commented Apr 1, 2020

@andreasgruenh Thank you for your answer. I'm still not sure what's the proper way to handle the state update. Should I use useEffect in the parent? Is there an example code somewhere on how to do this?

Thanks

@andrewgreenh
Copy link

Your intention is to initiate the refetch on the button click. Side effects are very much allowed in event handlers. The only restriction is, that they should not be called during the call of your component function ("during render").

const Parent = ({ data, refetch }) => {
  return <Child refetch={refetch} />
}
const Child = ({ refetch }) => {
  const onClick = () => refetch()
  return <button onClick={onClick}>Refetch Data</button>
}

This way your components are being rendered without side effects. Only when a user clicks a button, the refetch function is called (which sets some states inside of apollo).

@hwillson
Copy link
Member

hwillson commented Apr 3, 2020

Fixed in #3902, and released in React Apollo 3.1.4. Thanks all!

@hwillson hwillson closed this as completed Apr 3, 2020
@cmakohon
Copy link

cmakohon commented Apr 3, 2020

Updated my version to 3.1.4 this morning, but I am unfortunately still facing the same issue.

Screen Shot 2020-04-03 at 9 32 04 AM

@dannycochran
Copy link

@hwillson the issue is still reproducible in our test repo when you upgrade to 3.1.4:

However, the warning message is slightly more informative:

Warning: Cannot update a component (`InnerComponent`) while rendering a different component (`InnerComponent`). To locate the bad setState() call inside `InnerComponent`, follow the stack trace as described in https://fb.me/setstate-in-render
    in InnerComponent (at src/index.tsx:42)
    in WrapperComponent (at src/index.tsx:53)
    in ApolloProvider (at src/index.tsx:47)

@hwillson
Copy link
Member

hwillson commented Apr 4, 2020

Thanks all - investigating now.

@hwillson
Copy link
Member

@cmakohon @dannycochran 3.1.5 has been released and should fully address this. Let me know if you're still noticing issues. Thanks!

@Chedvaka
Copy link

Hi,
I upgrade my apollo/react-hooks to 3.1.5,
and I still have this warning:
image

I dont use react-apollo itself in my project - but I use -
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.5",
"@apollo/react-testing": "^3.1.4",
"apollo-boost": "^0.4.7",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link": "^1.2.13",
"apollo-link-http": "^1.5.16",
"apollo-link-schema": "^1.2.4",

@tdeekens
Copy link

Thanks a lot. This works for me. Confirming with CI now 😄. @Chedvaka maybe try to make sure that you resolve to the right version via yarn why apollo/react-hooks and maybe add a resolution.

@Chedvaka
Copy link

Chedvaka commented Apr 16, 2020

@tdeekens this is the output for yarn why @apollo/react-hooks:

image

seems like the correct version is installed but I'm still getting the issue.
I'm using @apollo/react-hooks directly, not through apollo-client.

@tdeekens
Copy link

Ok. Then I don't know why you're seing the error and if it's related to apollo itself. Also cause your stack trace doesn't have apollo at the top but your user context provider.

@Chedvaka
Copy link

@tdeekens, I think that my stack trace is similar to @cmakohon stack trace
@cmakohon does it work for you now?

@layerssss
Copy link

@hwillson Thanks! Could you release the patch for @apollo/react beta branch? (^4.0.0-beta.1)

@fdev
Copy link

fdev commented Apr 20, 2020

I believe I'm having this same issue on @apollo/client (version 3.0.0-beta.43). Will the fixes made here be ported to the new client as well, or should I create a new issue there?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug confirmed has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository in-progress
Projects
No open projects
Client Team
In progress
Development

No branches or pull requests