How to solve “Can’t perform a React state update on an unmounted component”

The scenario

– Let’s imagine that you have a card component that you could show and hide with a button.
– When the component become visible an asynchronous function is called to get current time.
– Once the fetch is done, it will update the card showing current time.

The problem

– Let’s say that this asynchronous function is taking a few seconds to bring data back, and at meantime the user decides to `hide` (or more appropriate thing to use is unmount) the card by clicking the button.
– when fetch is done, the asynchronous function will call setState to update the component that is no longer there and you will receive the nasty warning telling you that React can’t perform a React state update on an unmounted component.

And it makes perfect sense since the component and it’s DOM is not there any more.

 

The solution

– Use React refs to determine if the component is mounted or not.

./App.js

import "./styles.css";
import Card from "./card";
import React, { useState } from "react";

export default function App() {
  const [cardVisible, setCardVisible] = useState(true);
  const [buttonText, setButtonText] = useState("Hide card");

  const setVisibility = () => {
    setButtonText(buttonText === "Show card" ? "Hide card" : "Show card");
    setCardVisible(!cardVisible);
  };

  return (
    <div className="App">
      <button onClick={setVisibility}>{buttonText}</button>
      {cardVisible && <Card />}
    </div>
  );
}

./card.js

import React, { useEffect, useRef, useState } from "react";

const card = () => {
  const [data, setData] = useState("loading ...");
  const mounted = useRef(false);

  async function fetchData() {
    console.log("`fetchData` called!");
    fetch(
      "https://www.toni-develops.com/external-files/examples/service-workers/delayed-response.php"
    ).then((response) => {
      response.text().then(function (text) {
        console.log(">>>", mounted.current);
        if (mounted.current === null) return;
        console.log("Fetch response is available!");
        setData(text);
      });
    });
  }

  useEffect(() => {
    fetchData();
  });

  return (
    <div ref={mounted} className="card">
      <p>{data}</p>
    </div>
  );
};

export default card;
what we just did:

– Line 5 – we added mounted param which is a reference to any DOM object in this component. If the component is not mounted mounted.current will return null
– Line 26 – we just add the actually reference tag to any DOM object that is constantly visible in our component. In most cases this is just the card wrapping div.
– Line 14 – we just check if the component exist or not before performing stateUpdate

Obviously we could also use the old fashion DOM way and use querySelector to check if the element is actually there but I guess useRefs is the more “React way” of doing this.

The entire example could be seen here:

Leave a Reply