Most common memory leak in React

The Problem

I’ve seen this quite a few times: user gets to a page, that page has a component that triggers an asynchronous API call, the user leaves before that call resolves, the API returns, and React tries to update the state… of a component that doesn’t exist anymore.

Here’s a little app to help us visualize the issue(it’s contrived, I know). When the user clicks on mount, we’ll make an HTTP call to retrieve a random profile and display the User component on the screen. If we click on unmount, we remove the User component from the screen. Nothing exciting here!

application displaying random user profile

import { useState, useEffect, Fragment } from 'react';

function User() {
  const [user, setUser] = useState();

  useEffect(() => {
    window
      .fetch(`https://randomuser.me/api/`)
      .then((res) => res.json())
      .then((data) => {
        setUser(data.results[0]);
      });
  }, []);

  return (
    <div style={{ marginBottom: '15px' }}>
      {Boolean(user) && (
        <Fragment>
          <img src={user.picture.large} alt={`${user.name.first}`} />
          <div>{`Name: ${user.name.first} ${user.name.last}`}</div>
          <div>{`E-mail: ${user.email}`}</div>
        </Fragment>
      )}
    </div>
  );
}

export default function App() {
  const [shouldDisplayUser, setShouldDisplayUser] = useState(false);
  return (
    <div className="App">
      {shouldDisplayUser && <User />}
      <button
        style={{ marginRight: '10px' }}
        onClick={() => setShouldDisplayUser(true)}
      >
        Mount
      </button>
      <button onClick={() => setShouldDisplayUser(false)}>Unmount</button>
    </div>
  );
}

Now, let’s see what happens when we unmount the User component before the promise resolves.

application showing the error state

Tip: If you are on a fast and stable connection, you’ll have problems replicating this issue. I recommend that you throttle your internet speed using Chrome’s network tab. I’m using “slow 3G”.

Here’s what the error says: 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. at User (https://fq0td.csb.app/src/App.js:27:39)

Props to the React team; the error message is spot on. Let’s put it into even more mundane terms for our specific use case: “You are trying to update the state of a non-existent component. There’s nothing to update”.

How do we fix it?

Ideally, we should cancel the promise, but Promises are uncancellable in JavaScript; we need a plan B. How about if we check that the component is mounted before updating the state? That sounds promising.

The standard solution for checking that a component is mounted involves using the useEffect and useRef hooks together. In summary, we create an empty ref that will be set to TRUE when the component mounts and to FALSE when it unmounts. We rely on useEffect to know when mounting/unmounting occurs. Finally, we need to check the value of the ref before setting the state, like this:

...
function User() {
  const [user, setUser] = useState();
  const isMounted = useRef();  useEffect(() => {    // on mount, set ref to true    if (!isMounted.current) {      isMounted.current = true;    }    // on unmount, set ref to false    return () => (isMounted.current = false);  }, []);
  useEffect(() => {
    window
      .fetch(`https://randomuser.me/api/`)
      .then((res) => res.json())
      .then((data) => {
        if (isMounted.current) {          setUser(data.results[0]);        }        console.log("promise resolved");
      });
  }, []);
...
}
...

That’s it! We are now making sure that the component is on the screen before updating the state.

application displaying random user profile

Tip: you could extract the isMounted logic into a custom hook


Randy Perez

I am a Software Developer from Santo Domingo, Dominican Republic. I have been programming for 8+ years and professionally for the last 6+ years. Currently, I work for BairesDev an international outsourcing company that lends my service to Pinterest, a social media company based in San Francisco.