Все reactjs разработчики, кто имеет дело с интерактивностью между бэком и фронтом рано или поздно встречаются, или встречались, или встретятся со следующей ошибкой:

Если дословно, то получится так:
Предупреждение. Невозможно выполнить обновление состояния React для неустановленного компонента. Это не операция, но она указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в функции очистки useEffect.
На самом деле все достаточно просто, нужно всего лишь обратить внимание на следующие словосочетания:
- Невозможно выполнить обновление состояния
- неустановленного компонента;
- отмените все подписки и асинхронные задачи
- очистки useEffect
В основе хуки. GO в под кат!
Итак:
- Все reactjs программисты знают что такое состояние (state) и что такое обновление(setState) тоже. Я не буду этом.
- Неустановленный компонент. Учитывая контекст ошибки первое что приходит на ум:
1) компонент которого нет;
2) компонент который мы не импортировали;
3) компонент который размонтировался;
Наш случай — 3 пункт. Без комментарий. - Подписки и асинхронные задачи. То есть функции которые что-то выполняют, например: изменяют состояние. Что касаемо асинхронных задач, то тут сразу на ум бросается async/await — это наш способ общения с бэком в побочном эффекте.
- Очистка useEffect. Я думаю все знают что такое return () => {} в useEffect. Таким образом, мы можем произвести какие-либо действия в возвращаемой функции эффекта при размонтировании компонента, например: запретить изменять состояние.
Воспроизведем ошибку:
Допустим, мы разрабатываем сайт с подгрузкой описания фильмов (не будем углубляться в API moviedb, а возьмем за основу конкретный фильм). У нас есть две странички:
Ссылки на обе странички доступны в навигационной панели, например в 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 и асинхронность async/await
Весь исходный код можно посмотреть здесь: https://gitlab.com/ImaGadzhiev/react-cant-perform
ссылка на оригинал статьи https://habr.com/ru/post/493496/
Добавить комментарий