-
Notifications
You must be signed in to change notification settings - Fork 48.5k
setState in useEffect causing a "React state update on an unmounted component" warning #15057
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Just to add a bit about why I think this is a bug. The recommendation from the warning is to cleanup any subscriptions inside useEffect in the effect destroy function. But in this codesandbox, you should see that useEffect destroy is only called after the setState is called. There is no way to clean that up afaik since we're not yet aware that the component is being unmounted. |
This code is definitely buggy btw. You’re passing [] as effect deps but close over both atom and selector. That’s not legit. I suggest you to adopt the Implementing subscriptions correctly is actually somewhat tricky. Check this for inspiration: https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189 Does that work better? |
I'll continue working on this codesandbox to try and minimise the code and isolate the issue better. I am aware of some of the subscription pitfalls, this code is not what I use in production version. This is just an attempt at finding a minimal set of code that triggers this warning without using the React APIs "incorrectly" (except for the thing you pointed about about effect's deps, but I doubt that's the issue, I'll check). It has something to do with the sync calls inside useEffect triggering calls to setStates causing components to unrender, etc. It's something along the lines of useEffect triggering a setState in parent component causing to unrender the child, but in the same tick setState getting called in the child component's useEffect's subscription, before the effect had the chance to cleanup. Not sure you're following, I can elaborate.. I guess I'm just inviting you to have a bit closer look to see that this is not bad logic in my code, but a potentially incorrect behaviour in React. I know there's 99% chance the issue is in my code, or something I'm doing that "you're not supposed to be doing", but I was hoping it's valuable to share this in case it is indeed an issue in React or a matter of missing documentation. I know how to fix this in my application (e.g. defer my router listener callbacks), I ~know how to implement subscriptions correctly (thanks for the link, that's very useful), I'm not interested in how to fix my code, I'm just interested to help reveal a potential bug.. thus the contrived code. |
I pointed this out because I’ve seen similar issues caused by a stale listener running after a fresh listener or something like it. So it’ll be way easier to debug after rules aren’t violated. |
Will fix that and reshare a codesandbox. Just found some new insight (pointing to the fact that the issue is in my code). Will post back soon(ish). |
Sounds a bit like #14750 (comment). |
No, not sure it's my code. Here's an updated, slightly smaller sandbox https://codesandbox.io/s/98lkm3731o (but with console.logs). If this helps at all, what I can see happening is the following: Notice how
The code that logs that is:
Hm, not sure where I'm going with this.. |
Btw, I've added all effect dependencies to the code now. |
The unexpected thing to me that these logs revealed is that my subscription callbacks are "getting incepted".. I get a subscription callback triggered while in the middle of sync handling of a subscription callback. |
Reduced the code further, removed the router bit and some effects: |
Oh now, it looks like an issue in my code though, since now the logs indicate subscription hasn't been cleaned up.. duh, this is so annoying. |
OK, I think your initial instict was right, Dan. It is a subscription problem. I thought it's sufficient to clean subscriptions up by removing the subscription, but you have to use the I can fix this upstream in tiny-atom now. Again, thanks for your time 🙏 Apologies for raising false alarms. |
Oh noes.. In my actual app, the fix is not sufficient, there might be something else going on. I'll have to continue digging a bit. |
For what it's worth, here's a further simplified sandbox with subscription fix mentioned above: https://codesandbox.io/s/421mlo34z7 but the React warning still being triggered. I'll continue investigating.. |
The issue is roughly something like this, all in the same tick (I think?):
You can see all that here: I mean, I get this looks like some dodgy code on my part 😨 But it doesn't look "incorrect", or does it? |
I believe step 2 happens because React calls |
No, that's not right, I'll shut up now. Update: it is right, flushPassiveEffects is what triggers the "special sequence" in this case leading to the warning. |
Minor note — you might find it easier to debug if you use |
Ok so essentially you're saying |
Actually wait no. The previous effect can't set this effect's |
We're inside a change handler set up by an effect. That change handler sets the state. This flushes the passive effects, leading to unmount of this component. And therefore continuing with the In the unmount case, I think this is something we should fix in React. If flushing passive effects led to unmounting of the component with the currently called state setter, it makes no sense to try to apply that update. It'll never happen anyway. |
Phew 😅 Yes, that makes sense. I understand why the warning is triggered now. |
What are passive effects actually? |
useEffect is what we call “passive effects”. It executes with a little delay to let the browser paint first. Effect doesn’t execute before its render. (That would be impossible because effects are defined in render.) Instead, as I mentioned, effects execute with a delay. But if you try to render again before the previous effects had a chance to flush, we flush them before processing the update. This helps ensure that the behavior is deterministic. So what you’re seeing is that a |
I have the same question when use axios get data, report:
|
@kaimiyang Please file a new issue with a CodeSandbox. It's likely unrelated to this one. |
@gaearon Thank you, this is my fault. ( I unmounted the father component, and updated a children component, and then warning report). Now it's okey! |
This appears fixed by #15650. I can't repro it anymore with |
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
The warning is triggered after an intricate sequence of events. I stumbled upon it by accident, assumed it was an error in my code, but then step by step removed everything in my application until I was left with a fairly small bit of code that doesn't seem to be doing anything illegal from the API point of view, yet is triggering a warning.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem:
https://codesandbox.io/s/q87882qv64
The example is my real application code trimmed down as much as possible to demonstrate the warning. So some of the code might be a bit nonsensical/contrived, but that's because I removed lots of surrounding code leaving only the relevant bits for reproducing the issue.
In other words, there might weird looking uses of useEffect and weird sequencing of things, but that sort of falls out of how I've structured my routes, state, components in the full app, etc.
What is the expected behavior?
I would like to know if
a) is this a React bug that I stumbled upon that should be fixed?
b) is this something I'm doing "wrong" and how I could fix that in my application (i.e. is this a real memory leak being caused because of the way I structured the code)
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
16.8.4
The text was updated successfully, but these errors were encountered: