ReactJS. Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memo

Good day, Habr!

All reactjs developers who deal with interactivity between the back and front meet sooner or later, or have met, or will encounter the following error:


Literally, it turns out like this:
Warning. Unable to update React state for an uninstalled component. This is not an operation, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the useEffect cleanup function.

In fact, everything is quite simple, you just need to pay attention to the following phrases:

  1. Unable to perform state update
  2. an uninstalled component;
  3. cancel all subscriptions and asynchronous tasks
  4. useEffect cleanup


At the heart of hooks. GO in under the cut!

So:

  1. All reactjs programmers know what condition (state) and that such an update (setState) too. I will not be that.
  2. . :
    1) ;
    2) ;
    3) ;
    โ€” 3 . .
  3. . - , : . , async/await โ€” .
  4. useEffect. return () => {} useEffect. , - , : .




Let's make a mistake: Let's say we are developing a site with movie descriptions uploaded (we will not go deep into the moviedb API , but take a specific movie as a basis). We have two pages:
Home

About us



Links to both pages are available in the navigation panel, for example, in the header. On the main page ("Home") there is communication with the back to upload information about the film.

/src/Pages/HomePage.js
import React, { useState, useEffect } from 'react';

import { MOVIE_DB_GET } from '../config';

const HomePage = () => {
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(MOVIE_DB_GET);
        const result = await response.json();
        console.log(result, 'result')
      } catch (e) {
        console.error(e.message)
      }
    };

    //  get-     
    fetchData();
  }, []);

  return (
    <main>
      <h2> </h2>
    </main>
  )
};

export default HomePage;


/src/Pages/AboutPage.js

import React from 'react';

const AboutPage = () => {
  return (
    <main>
      <h2> </h2>
      <p>
                
      </p>
    </main>
  )
};

export default AboutPage;


To display information through a request, it is necessary to write information to the state for subsequent rendering:

/src/Pages/HomePage.js

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

import { MOVIE_DB_GET } from '../config';

const HomePage = () => {
  // movie - react-;
  // setMovie -   react-
  const [ movie, setMovie ] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(MOVIE_DB_GET);
        const result = await response.json();
        console.log(result, 'result');
        setMovie(result);
      } catch (e) {
        console.error(e.message)
      }
    };

    //       
    fetchData();
  }, []);

  // descriptionMovie -  ,  view,         movie

  return (
    <main>
      <h2> </h2>
      <p> </p>

      {
        movie ? descriptionMovie() : false
      }
    </main>
  )
};

export default HomePage;


We will reproduce the error in the following way - we will switch from one route to another, that is, being on the page โ€œAbout Usโ€, we will go to the page โ€œHomeโ€ and immediately return again to the page โ€œAbout Usโ€ and voila โ€œ Can't perform ... .. โ€ .

The fact is that the server does not respond to requests instantly, asynchrony is used to still play the request in parallel with the necessary tasks. But in the case of a quick return to the About Us page, the Home component is unmounted, which means that the state for this component will be reset, but the asynchronous request will still launch setMovie, which is no longer there and will throw an error. The best solution is to prohibit the use of state updates when unmounting a component:

/src/Pages/HomePage.js

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

import { MOVIE_DB_GET } from '../config';

const HomePage = () => {
  // movie - react-;
  // setMovie -   react-
  const [ movie, setMovie ] = useState(null);

  useEffect(() => {
    let cleanupFunction = false;
    const fetchData = async () => {
      try {
        const response = await fetch(MOVIE_DB_GET);
        const result = await response.json();
        console.log(result, 'result')

        //     ,    
        if(!cleanupFunction) setMovie(result);
      } catch (e) {
        console.error(e.message)
      }
    };

    fetchData();

    //   useEffect
    return () => cleanupFunction = true;
  }, []);

  // descriptionMovie -  ,  view,         movie

  return (
    <main>
      <h2> </h2>
      <p> </p>

      {
        movie ? descriptionMovie() : false
      }
    </main>
  )
};

export default HomePage;


Total:


All source code can be viewed here: https://gitlab.com/ImaGadzhiev/react-cant-perform

All Articles