While writing tests for a React app I ran into the following warning:

Warning: setState(...): Can only update a mounted or mounting component. This usually
means you called setState() on an unmounted component. This is a no-op. Please check
the code for the BoatyMcBoatface component.

This warning is emitted, for example, when a component starts a REST call (or timer etc) and then gets unmounted before the call completes. Once the call does complete, it typically tries to do setState() on the component, which is not legal after it has been unmounted.

So the correct fix is to abort the REST call when the component unmounts, which is easy if you’re using axios or some other XMLHttpRequest wrapper. The problem was that I was using native fetch() which doesn’t have support for aborting the request (yet). I googled around for the status on cancellation and it turns out that just a few days ago a new API was been added1 to the DOM specification for this. This new API is a smaller scope compared to cancellation of promises for which discussions stalled a bit early this year.

This new DOM API allows you to create an AbortController that in turn allows you to pass an AbortSignal into the fetch() call. Later on you can call .abort() on the controller to cancel the request. If you used the new API from a React application, it would look like this:

componentDidMount() {
  this.controller = new AbortController();
  const signal = this.controller.signal;
  fetch(`/api/thing?id=${this.props.thingId}`, {signal}).then(res => {
    if (res.status == 400) {
      this.setState({ loading: false, actualThing: undefined });
      return;
    }
    return res.json();
  }).then(actualThing => {
    this.setState({ loading: false, actualThing });
  }).catch(err => {
    if (err.name == 'AbortError') {
      return;
    }
    // It's important to rethrow all other errors so you don't silence them!
    // For example, any error thrown by setState(), will pass through here.
    throw err;
  });
}

componentWillUnmount() {
  this.controller.abort();
}

I wanted to try this new API in my application, so I created a polyfill for it. The polyfill does not actually close the TCP connection on abort, it just throws the AbortError as seen above and then when the response arrives it is dropped silently (the real implementation will close the TCP socket though of course). This allows you to target the new API today and once the API is implemented in browsers you can just drop the polyfill and not modify your code. The polyfill is available here:

If you’re using webpack, you simply npm install --save abortcontroller-polyfill and then import 'abortcontroller-polyfill'; or require('abortcontroller-polyfill'); near the top of your client entrypoint .js or just include 'abortcontroller-polyfill' as an entrypoint itself (before your own entrypoint).

When I was googling around for the status on cancellation, I also noticed that Ron Buckton is presenting a very ambitious proposal (slides) at a TC39 meeting (which happens to be today!) that aims to unify cancellation, not just for DOM but for asynchronous functions, asynchronous iterators, webworkers, animation etc in such a way that they can be both synchronous and asynchronously observed (i.e. asking “was it cancelled?” verus “tell me if it cancels”). This work is based on cancellation in managed threads in the .NET Framework.

1: The final version of AbortController has been added to the DOM specification. The corresponding PR for the fetch specification is essentially done but not technically merged yet, waiting for testcases to be merged into web-platform-tests via PR #6484. Browser bugs tracking the implementation of AbortController is available here: Firefox: #1378342, Chromium: #750599, WebKit: #174980, Edge: #13009916.