react: isMounted is not enough to know if you can setState
For the sake of this, I will simplify the workflow into three stages:
- The component is mounting, you may call setState, isMounted is false
- The component is mounted, you may call setState, isMounted is true
- The component is unmounted, you must not call setState, isMounted is false
The particular case I’m worried about is when you trigger a promise (or similar delayed action) from componentWillMount:
componentWillMount: function () {
doSomething(props).then(function (url) {
if (this.isMounted()) {
this.setState({url: url});
}
}.bind(this));
}
The .then() could be called in two circumstances, synchronously in the same call stack, or asynchronously in a later stack (I know, mixing async and sync in the same signature is a bad idea, but for the sake of argument).
In the asynchronous case: the promise above could resolve at any time in the future, so in order to prevent calling setState when we aren’t allowed to, we add the isMounted() check. But what if the promise resolves while the component is still in the mounting state, outside of the componentWillMount stack?
- is it possible for this to happen? Is there a chance for other JS to run between componentWillMount and the component being mounted, or are they in the same cycle?
- if it can, what will happen? Will an error be shown?
In the synchronous case: we will enter the block still in the mounting state, which means isMounted() will be false and my state won’t be set.
Depending on the answers to my async questions I can see two outcomes:
- Async cannot return before the component is mounted: this is a minor inconvenience that can be solved by coding such that you know what’s async and what’s not.
- Async can return before being mounted: there is no way to know if
setState()is safe to call or not, asisMounted()is incomplete. The user has to manually track the state of the component and if it has been unmounted or not.
Thoughts? Have I invented an issue where none exists?
About this issue
- Original URL
- State: closed
- Created 10 years ago
- Reactions: 2
- Comments: 46 (6 by maintainers)
This discussion is a little old but I think we have a reasonable conclusion here.
If you can, we recommend that you use APIs that provide cancellation.
For example, axios lets you create a
CancelTokenfor all requests and thencancel()them when the component unmounts:I don’t agree cancellation is necessarily tedious—if it is, I suggest using a different library or asking the library you use to add a cancellation API.
This is the right way to solve this problem because it also eliminates a whole class of bugs.
For example, if you fetch data based on props, you should also cancel any requests for previous props after they change—or you risk getting the new response earlier than the older response, potentially displaying the wrong UI. Cancellation already solves these race conditions so this is one more reason to embrace it.
If you can’t (or choose not to) implement cancellation for some reason, then setting a field is the way to go.
Yes, this is clunky, but this clunkiness reminds you that:
In any case, React doesn’t throw errors when this happens anymore. But we will intentionally keep the suboptimal case clunky so that the developers have incentives to implement the right solution (cancellation).
There are some edge cases related to libraries like Redux.
I would just recommend to not perform side effects in
componentWillUnmountthat might affect the tree being unmounted. This sounds like potential performance issue anyway. You can enqueue those side effects and flush them on the next tick, if you’d like.So, to sum up:
isMounted()is not available for ES6 classes anyway.axiosmake it easy.I think this covers everything in this issue, so I’m closing it. Thanks to everyone for the discussion!
Just to add to the confusion: in
componentWillUnmount,isMounted()is true and you are not allowed tosetState()This has bitten me as well. If you have any actions triggered by non-react-events then your calls to
setStatein those handlers are handled synchronously which may trigger the component to become unmounted halfway through your handler causing any subsequent calls tosetStateto emit a warning. This is very unintuitive and I would expectsetStateto always be asynchronous. The documentation even indicates that this is the likely scenario. The abstraction, as is, is very leaky because you need to know if your handler will invoke a call tosetStatesomewhere in its call stack. For example,Checking
this.isMountedshould really not be necessary as now I must code in fear that my component will be unmounted at any point! This is exacerbated by the fact thatthis.isMountedhas been deprecated thus there is almost no way to block these warnings aside from tracking my ownisMountedmanually!Or, a
setStateSafe, which does just that?I agree
componentDidMountis a better place to set the flag totrue.+1 for setStateIfMounted for small harmless callbacks. Making the user install another library for promise cancellation or track the unmounting is tedious.
We explicitly say in the documentation that the goal of
isMountedis to guard against setState: http://facebook.github.io/react/docs/component-api.html#ismountedSo it’s not right that it doesn’t work in all cases