- PVSM.RU - https://www.pvsm.ru -

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

Доброго времени суток!

Все reactjs [1] разработчики, кто имеет дело с интерактивностью между бэком и фронтом рано или поздно встречаются, или встречались, или встретятся со следующей ошибкой:
ReactJS. Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memo - 1

Если дословно, то получится так:

Предупреждение. Невозможно выполнить обновление состояния React для неустановленного компонента. Это не операция, но она указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в функции очистки useEffect.

На самом деле все достаточно просто, нужно всего лишь обратить внимание на следующие словосочетания:

  1. Невозможно выполнить обновление состояния
  2. неустановленного компонента;
  3. отмените все подписки и асинхронные задачи
  4. очистки useEffect

В основе хуки. GO в под кат!

Итак:

  1. Все reactjs программисты знают что такое состояние [2] (state) и что такое обновление(setState) тоже. Я не буду этом.
  2. Неустановленный компонент. Учитывая контекст ошибки первое что приходит на ум:
    1) компонент которого нет;
    2) компонент который мы не импортировали;
    3) компонент который размонтировался;
    Наш случай — 3 пункт. Без комментарий.
  3. Подписки и асинхронные задачи. То есть функции которые что-то выполняют, например: изменяют состояние. Что касаемо асинхронных задач, то тут сразу на ум бросается async/await — это наш способ общения с бэком в побочном эффекте.
  4. Очистка useEffect [3]. Я думаю все знают что такое return () => {} в useEffect [4]. Таким образом, мы можем произвести какие-либо действия в возвращаемой функции эффекта при размонтировании компонента, например: запретить изменять состояние.

Воспроизведем ошибку:

Допустим, мы разрабатываем сайт с подгрузкой описания фильмов (не будем углубляться в API moviedb [5], а возьмем за основу конкретный фильм). У нас есть две странички:

Домой

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

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

Ссылки на обе странички доступны в навигационной панели, например в header. На главной страничке («Домой») происходит общение с бэком для подгрузки информации о фильме.

/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;

Для отображения информации посредством запроса необходимо записать информацию в состояние для последующего рендера:

/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;

Воспроизведем ошибку следующим путем — переключимся с одного маршрута на другой, то есть находясь на страничке “О нас”, мы перейдем на страничку “Домой” и незамедлительно вернемся снова на страничку “О нас” и вуаля “Can’t perform …..”.

Дело в том, что на запросы сервер не отвечает мгновенно, используется асинхронность, чтобы все таки воспроизвести запрос параллельно необходимым задачам. Но в случае быстрого возврата на страничку “О нас”, компонент “Домой” размонтируется, а значит состояние для данного компонента сброситься, но асинхронность запроса все же запустит setMovie, которого больше нет и выкинет ошибку. Оптимальным решением является запретить использовать обновление состояния при размонтировании компонента:

/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;

Итого:

  • Особенности размонтирования компонента и обновление состояния
  • Взаимодействие конструкции try/catch [6] и асинхронность async/await [7]

Весь исходный код можно посмотреть здесь: https://gitlab.com/ImaGadzhiev/react-cant-perform [8]

Автор: Имран Гаджиев

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/350452

Ссылки в тексте:

[1] reactjs: https://ru.reactjs.org/

[2] состояние: https://ru.reactjs.org/docs/hooks-state.html

[3] useEffect: https://ru.reactjs.org/docs/hooks-effect.html

[4] return () => {} в useEffect: https://ru.reactjs.org/docs/hooks-effect.html#effects-with-cleanup

[5] API moviedb: https://developers.themoviedb.org/3/getting-started/introduction

[6] try/catch: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch

[7] async/await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

[8] https://gitlab.com/ImaGadzhiev/react-cant-perform: https://gitlab.com/ImaGadzhiev/react-cant-perform

[9] Источник: https://habr.com/ru/post/493496/?utm_source=habrahabr&utm_medium=rss&utm_campaign=493496