react: isMounted is not enough to know if you can setState

For the sake of this, I will simplify the workflow into three stages:

  1. The component is mounting, you may call setState, isMounted is false
  2. The component is mounted, you may call setState, isMounted is true
  3. 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:

  1. 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.
  2. Async can return before being mounted: there is no way to know if setState() is safe to call or not, as isMounted() 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)

Most upvoted comments

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 CancelToken for all requests and then cancel() them when the component unmounts:

componentDidMount() {
  this.cancelSource = axios.CancelToken.source();
  axios.get('/user/12345', {
    cancelToken: this.cancelSource.token
  }).then(
    // ...
  );
}

componentWillUnmount() {
   this.cancelSource.cancel()
}

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:

  • Your application is using network less efficiently.
  • Your application might have more memory leaks.
  • Your application might have more race conditions.

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 componentWillUnmount that 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:

  • It is only a warning, not an error now.
  • isMounted() is not available for ES6 classes anyway.
  • The right fix is to implement cancellation, and libraries like axios make it easy.
  • You can always either ignore the warning, or use a field.
  • For cases like Redux, it’s better to dispatch on the next tick and let the tree unmount first.

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 to setState()

This has bitten me as well. If you have any actions triggered by non-react-events then your calls to setState in those handlers are handled synchronously which may trigger the component to become unmounted halfway through your handler causing any subsequent calls to setState to emit a warning. This is very unintuitive and I would expect setState to 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 to setState somewhere in its call stack. For example,

componentDidMount() {
    window.addEventListener(...);
}

componentWillUnmount() {
    window.removeEventListener(...);
}

handleWindowKeyboardPress(event) { 
    this.props.onKeyPressed(); // triggers a datastore update which emits a change event that ends up invoking a setState call in a parent somewhere

    // component is now unmounted

    this.setState(...) // -> triggers warning
    // fix is this.isMounted && this.setState(...)
}

Checking this.isMounted should 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 that this.isMounted has been deprecated thus there is almost no way to block these warnings aside from tracking my own isMounted manually!

Or, a setStateSafe, which does just that?

I agree componentDidMount is a better place to set the flag to true.

+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 isMounted is to guard against setState: http://facebook.github.io/react/docs/component-api.html#ismounted

So it’s not right that it doesn’t work in all cases