title: Prevent "React state update on unmounted component" warning when setting state on async callback
body: <p>I want to trigger an asynchronous operation from an event handler on a component and after that operation completes, update some UI state in that component. But the component may be removed from the DOM at any time, due to user navigating to another page. If that happens while the operation hasn't completed yet, React logs this warning:</p>
  <blockquote>
  <p>Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.</p>
  </blockquote>
  <p>Here's a reproducible example:</p>
  <pre class="lang-js prettyprint-override"><code>import { useState } from &quot;react&quot;;
  import ReactDOM from &quot;react-dom&quot;;
  // The router lib is a detail; just to simulate navigating away.
  import { Link, Route, BrowserRouter } from &quot;react-router-dom&quot;;
  
  function ExampleButton() {
    const [submitting, setSubmitting] = useState(false);
  
    const handleClick = async () =&gt; {
      setSubmitting(true);
      await doStuff();
      setSubmitting(false);
    };
  
    return (
      &lt;button onClick={handleClick} disabled={submitting}&gt;
        {submitting ? &quot;Submitting&quot; : &quot;Submit&quot;}
      &lt;/button&gt;
    );
  }
  
  function doStuff() {
    // Suppose this is a network request or some other async operation.
    return new Promise((resolve) =&gt; setTimeout(resolve, 2000));
  }
  
  function App() {
    return (
      &lt;BrowserRouter&gt;
        &lt;nav&gt;
          &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt; | &lt;Link to=&quot;/other&quot;&gt;Other&lt;/Link&gt;
        &lt;/nav&gt;
        &lt;Route path=&quot;/&quot; exact&gt;
          Click the button and go to &quot;Other&quot; page
          &lt;br /&gt;
          &lt;ExampleButton /&gt;
        &lt;/Route&gt;
        &lt;Route path=&quot;/other&quot;&gt;Nothing interesting here&lt;/Route&gt;
      &lt;/BrowserRouter&gt;
    );
  }
  
  ReactDOM.render(&lt;App /&gt;, document.querySelector(&quot;#root&quot;));
  </code></pre>
  <p>You can see and run the example <a href="https://codesandbox.io/s/setstate-warning-d13oz" rel="nofollow noreferrer">here</a>. If you click the Submit button and then the &quot;Other&quot; link before 2 seconds pass, you should see the warning on the console.</p>
  <p>Is there an idiomatic way or pattern for dealing with these scenarios where a state update is needed after an async operation?</p>
  <h2>What i've tried</h2>
  <p>My first attempt to fix this warning was to track whether the component has been unmounted or not using a mutable ref and a <code>useEffect()</code> hook:</p>
  <pre class="lang-js prettyprint-override"><code>function ExampleButton() {
    const [submitting, setSubmitting] = useState(false);
    const isMounted = useRef(true);
  
    useEffect(() =&gt; {
      return () =&gt; {
        isMounted.current = false;
      };
    }, []);
  
    const handleClick = async () =&gt; {
      setSubmitting(true);
      await doStuff();
      if (isMounted.current) setSubmitting(false);
    };
  
    return (
      &lt;button onClick={handleClick} disabled={submitting}&gt;
        {submitting ? &quot;Submitting&quot; : &quot;Submit&quot;}
      &lt;/button&gt;
    );
  }
  </code></pre>
  <p>Notice the conditional call to <code>setSubmitting()</code> after the <code>doStuff()</code> call.</p>
  <p>This solution <a href="https://codesandbox.io/s/setstate-warning-ugly-fix-bikqv" rel="nofollow noreferrer">works</a>, but i'm not too satisfied with it because:</p>
  <ul>
  <li>It's quite bolerplate-ish. All the manual <code>isMounted</code> tracking seems like a low-level detail, unrelated to what this component is trying to do, and not something i'd want to repeat on other places that need a similar async operation.</li>
  <li>Even if the boilerplate was hidden into a custom <code>useIsMounted()</code> hook, is seems that <a href="https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html" rel="nofollow noreferrer">isMounted is an antipattern</a>. Yes, the article is talking about the <code>Component.prototype.isMounted</code> method, which is not present on function components like the one i'm using here, but i'm basically emulating the same function with the <code>isMounted</code> ref.</li>
  </ul>
  <p>Update: i've also seen the pattern of having <a href="https://www.robinwieruch.de/react-hooks-fetch-data" rel="nofollow noreferrer">a <code>didCancel</code> boolean variable</a> inside the useEffect function, and using that to conditionally do stuff after the async function or not (because of an unmount or updated dependencies). I can see how this approach, or using a cancellable promise, would work nice in cases where the async operation is confined to a <code>useEffect()</code> and is triggered by component mount/update. But i cannot see how they would work in cases when the async operation is triggered on an event handler. The useEffect cleanup function should be able to see the <code>didCancel</code> variable, or the cancellable promise, so they would need to be lifted up to the component scope, making them virtually the same as the <code>useRef</code> approach mentioned above.</p>
  <p>So i'm kind of lost on what to do here. Any help will be appreciated! :D</p>