React useEffect вызывает: невозможно выполнить обновление состояния React для несмонтированного компонента

При извлечении данных я получаю: Не удается выполнить обновление состояния React для несмонтированного компонента. Приложение все еще работает, но реакция предполагает, что я могу вызвать утечку памяти.

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."

Почему я продолжаю получать это предупреждение?

Я попытался исследовать эти решения:

https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal

https://developer.mozilla.org/en-US/docs/Web/API/AbortController

но это все еще давало мне предупреждение.

const  ArtistProfile = props => {
  const [artistData, setArtistData] = useState(null)
  const token = props.spotifyAPI.user_token

  const fetchData = () => {
    const id = window.location.pathname.split("/").pop()
    console.info(id)
    props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
    .then(data => {setArtistData(data)})
  }
  useEffect(() => {
    fetchData()
    return () => { props.spotifyAPI.cancelRequest() }
  }, [])
  
  return (
    <ArtistProfileContainer>
      <AlbumContainer>
        {artistData ? artistData.artistAlbums.items.map(album => {
          return (
            <AlbumTag
              image = {album.images[0].url}
              name = {album.name}
              artists = {album.artists}
              key = {album.id}
            />
          )
        })
        : null}
      </AlbumContainer>
    </ArtistProfileContainer>
  )
}

Редактировать:

В моем файле API я добавил AbortController() и использовал signal, чтобы я мог отменить запрос.

export function spotifyAPI() {
  const controller = new AbortController()
  const signal = controller.signal

// code ...

  this.getArtist = (id) => {
    return (
      fetch(
        `https://api.spotify.com/v1/artists/${id}`, {
        headers: {"Authorization": "Bearer " + this.user_token}
      }, {signal})
      .then(response => {
        return checkServerStat(response.status, response.json())
      })
    )
  }

  // code ...

  // this is my cancel method
  this.cancelRequest = () => controller.abort()
}

Мой spotify.getArtistProfile() выглядит так

this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
  return Promise.all([
    this.getArtist(id),
    this.getArtistAlbums(id,includeGroups,market,limit,offset),
    this.getArtistTopTracks(id,market)
  ])
  .then(response => {
    return ({
      artist: response[0],
      artistAlbums: response[1],
      artistTopTracks: response[2]
    })
  })
}

но поскольку мой сигнал используется для отдельных вызовов API, которые разрешаются в Promise.all я не могу abort() это обещание, поэтому я всегда буду устанавливать состояние.

Предупреждение связано с тем, что обещание getArtistProfile() возвращает разрешение после размонтирования компонента. Либо отмените этот запрос, либо, если это невозможно, добавьте проверку в обработчике .then(), чтобы setArtistData() не вызывался, если компонент был размонтирован.

ᅙᄉᅙ 02.03.2019 02:36

Невозможно объяснить, почему это происходит, не зная больше о вашем приложении за пределами этого компонента. Нам нужно знать, что заставляет этот компонент монтироваться/размонтироваться. Что происходит в приложении, когда вы получаете ошибку?

Ryan Cogswell 02.03.2019 14:23

@ııı Как мне проверить, размонтировался ли компонент?

Ryan Sam 02.03.2019 23:21

Это не настоящая утечка памяти, а, скорее всего, ложное предупреждение — поэтому команда React уберет предупреждение в следующем релизе. См. пиар

Katie 10.12.2021 07:11
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
104
4
240 143
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Вы можете попробовать установить такое состояние и проверить, смонтирован ли ваш компонент или нет. Таким образом, вы уверены, что если ваш компонент размонтирован, вы не пытаетесь что-то получить.

const [didMount, setDidMount] = useState(false); 

useEffect(() => {
   setDidMount(true);
   return () => setDidMount(false);
}, [])

if (!didMount) {
  return null;
}

return (
    <ArtistProfileContainer>
      <AlbumContainer>
        {artistData ? artistData.artistAlbums.items.map(album => {
          return (
            <AlbumTag
              image = {album.images[0].url}
              name = {album.name}
              artists = {album.artists}
              key = {album.id}
            />
          )
        })
        : null}
      </AlbumContainer>
    </ArtistProfileContainer>
  )

Надеюсь, что это поможет вам.

didMount будет true в несмонтированном состоянии.
ᅙᄉᅙ 02.03.2019 23:37

Можете ли вы объяснить немного подробнее, почему?

Mertcan Diken 02.03.2019 23:40

Компонент монтируется, затем запускается эффект и устанавливает didMount на true, затем компонент размонтируется, но didMount никогда не сбрасывается.

ᅙᄉᅙ 02.03.2019 23:44

Я думаю, вы имели в виду setDidMount(false). Но проблема в том, что замыкание обработчика .then() будет иметь ссылку на более раннее didMount

ᅙᄉᅙ 03.03.2019 00:14

Это был метод, с помощью которого я решил проблему SSR в своем приложении, и подумал, что он подойдет и для этого случая. Если не обещание должно быть отменено, я думаю.

Mertcan Diken 03.03.2019 00:18

Ошибка: Rendered more hooks than during the previous render.

cmcodes 01.03.2021 05:42
Ответ принят как подходящий

Разделение AbortController между fetch() запросами — правильный подход.
Когда Любые из Promise прерывается, Promise.all() отклоняется с AbortError:

function Component(props) {
  const [fetched, setFetched] = React.useState(false);
  React.useEffect(() => {
    const ac = new AbortController();
    Promise.all([
      fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
      fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
    ]).then(() => setFetched(true))
      .catch(ex => console.error(ex));
    return () => ac.abort(); // Abort both fetches on unmount
  }, []);
  return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src = "//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src = "//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>

Мне помогла очистка состояния в размонтировании компонента.

 const [state, setState] = useState({});

useEffect(() => {
    myFunction();
    return () => {
      setState({}); // This worked for me
    };
}, []);

const myFunction = () => {
    setState({
        name: 'Jhon',
        surname: 'Doe',
    })
}

Я не понимаю логики, но это работает.

Getzel 18.03.2021 01:39

Объясните кто-нибудь, пожалуйста.

entithat 10.06.2021 01:33

О, кажется, я понял. Функция обратного вызова в useEffect будет выполняться только тогда, когда компонент будет выгружен. Вот почему мы можем получить доступ к реквизитам состояния name и surname до того, как компонент будет выгружен.

entithat 10.06.2021 01:48

Когда вы возвращаете функцию из useEffect, эта функция будет выполняться при размонтировании компонента. Таким образом, воспользовавшись этим, вы устанавливаете свое состояние на пустое. Делая это, всякий раз, когда вы покидаете этот экран или компонент размонтируется, состояние будет пустым, поэтому компоненты вашего экрана не будут пытаться повторно рендериться снова. надеюсь, это поможет

Joseph Ajibodu 29.07.2021 02:54

это сработало бы, даже если вы вернете пустую функцию из useEffect. React просто гарантирует, что вы возвращаете функцию из useEffect для выполнения очистки. неважно, какую очистку вы выполняете

Pankaj 17.09.2021 05:15

У меня была аналогичная проблема с прокруткой вверх, и ответ @CalosVallejo решил ее :) Большое спасибо!!

const ScrollToTop = () => { 

  const [showScroll, setShowScroll] = useState();

//------------------ solution
  useEffect(() => {
    checkScrollTop();
    return () => {
      setShowScroll({}); // This worked for me
    };
  }, []);
//-----------------  solution

  const checkScrollTop = () => {
    setShowScroll(true);
 
  };

  const scrollTop = () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
 
  };

  window.addEventListener("scroll", checkScrollTop);

  return (
    <React.Fragment>
      <div className = "back-to-top">
        <h1
          className = "scrollTop"
          onClick = {scrollTop}
          style = {{ display: showScroll }}
        >
          {" "}
          Back to top <span>&#10230; </span>
        </h1>
      </div>
    </React.Fragment>
  );
};

у вас есть window.addEventListener("scroll", checkScrollTop); рендер

Filip Kováč 25.10.2021 10:53

Эта ошибка возникает, когда вы выполняете обновление состояния текущего компонента после перехода к другому компоненту:

Например

  axios
      .post(API.BASE_URI + API.LOGIN, { email: username, password: password })
      .then((res) => {
        if (res.status === 200) {
          dispatch(login(res.data.data)); // line#5 logging user in
          setSigningIn(false); // line#6 updating some state
        } else {
          setSigningIn(false);
          ToastAndroid.show(
            "Email or Password is not correct!",
            ToastAndroid.LONG
          );
        }
      })

В приведенном выше случае в строке № 5 я отправляю действие login, которое, в свою очередь, направляет пользователя на панель инструментов, и, следовательно, экран входа в систему теперь отключается. Теперь, когда React Native достигает строки № 6 и видит, что состояние обновляется, он громко кричит, как мне это сделать, login component больше нет.

Решение:

  axios
      .post(API.BASE_URI + API.LOGIN, { email: username, password: password })
      .then((res) => {
        if (res.status === 200) {
          setSigningIn(false); // line#6 updating some state -- moved this line up
          dispatch(login(res.data.data)); // line#5 logging user in
        } else {
          setSigningIn(false);
          ToastAndroid.show(
            "Email or Password is not correct!",
            ToastAndroid.LONG
          );
        }
      })

Просто переместите обновление состояния реакции выше, переместите строку 6 вверх по строке 5.
Теперь состояние обновляется перед уходом пользователя. ПОБЕДА ПОБЕДА

Например, у вас есть компонент, который выполняет какие-то асинхронные действия, затем записывает результат в состояние и отображает содержимое состояния на странице:

export default function MyComponent() {
    const [loading, setLoading] = useState(false);
    const [someData, setSomeData] = useState({});
    // ...
    useEffect(() => {
        setLoading(true);
        someResponse = await doVeryLongRequest(); // it needs some time
        // When request is finished:
        setSomeData(someResponse.data); // (1) write data to state
        setLoading(false); // (2) write some value to state
    }, []);

    return (
        <div className = {loading ? "loading" : ""}>
            {someData}
            <a href = "SOME_LOCAL_LINK">Go away from here!</a>
        </div>
    );
}

Допустим, пользователь нажимает на какую-то ссылку, когда doVeryLongRequest() все еще выполняется. MyComponent размонтирован, но запрос все еще жив, и когда он получает ответ, он пытается установить состояние в строках (1) и (2) и пытается изменить соответствующие узлы в HTML. Мы получим ошибку от темы.

Мы можем исправить это, проверив, смонтирован ли компонент или нет. Давайте создадим ссылку componentMounted (строка (3) ниже) и установим ее true. Когда компонент размонтирован, мы установим для него значение false (строка (4) ниже). И давайте проверять переменную componentMounted каждый раз, когда мы пытаемся установить состояние (строка (5) ниже).

Код с исправлениями:

export default function MyComponent() {
    const [loading, setLoading] = useState(false);
    const [someData, setSomeData] = useState({});
    const componentMounted = useRef(true); // (3) component is mounted
    // ...
    useEffect(() => {
        setLoading(true);
        someResponse = await doVeryLongRequest(); // it needs some time
        // When request is finished:
        if (componentMounted.current){ // (5) is component still mounted?
            setSomeData(someResponse.data); // (1) write data to state
            setLoading(false); // (2) write some value to state
        }
        return () => { // This code runs when component is unmounted
            componentMounted.current = false; // (4) set it to false when we leave the page
        }
    }, []);

    return (
        <div className = {loading ? "loading" : ""}>
            {someData}
            <a href = "SOME_LOCAL_LINK">Go away from here!</a>
        </div>
    );
}

Я не уверен в этой информации, но установка переменной componentMounted таким образом, вероятно, вызовет следующее предупреждение: «Назначения переменной 'componentMounted' внутри React Hook useEffect будут потеряны после каждого рендеринга. Чтобы сохранить значение с течением времени, сохраните его в хуке useRef и сохраните изменяемое значение в свойстве .current. ..." В этом случае может потребоваться установка его как состояния, как рекомендуется здесь: stackoverflow.com/questions/56155959/…

Bonellia 11.08.2021 15:14

Это допустимо, но вы должны использовать хук useRef, чтобы сохранить значение componentMounted (изменяемое значение) или переместить объявление переменной componentMounted внутрь useEffect

Diego Lope Loyola 13.10.2021 07:12

Согласитесь, ребята. Фиксированный

Dzmitry Kulahin 13.10.2021 13:03

Если пользователь уйдет или что-то еще приведет к уничтожению компонента до того, как асинхронный вызов вернется и попытается установить для него setState, это вызовет ошибку. Как правило, это безвредно, если это действительно асинхронный вызов с поздним завершением. Есть несколько способов заглушить ошибку.

Если вы реализуете такой хук, как useAsync, вы можете объявить свои useStates с помощью let вместо const, а в деструкторе, возвращаемом useEffect, установить функцию (функции) setState в неактивную функцию.


export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
  let [parameters, setParameters] = useState(rest);
  if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
    setParameters(rest);

  const refresh: () => void = useCallback(() => {
    const promise: Promise<T | void> = gettor
      .apply(null, parameters)
      .then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
      .catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
    setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
    return promise;
  }, [gettor, parameters]);

  useEffect(() => {
    refresh();
    // and for when async finishes after user navs away //////////
    return () => { setTuple = setParameters = (() => undefined) } 
  }, [refresh]);

  let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
  return tuple;
}

Однако это не будет хорошо работать в компоненте. Там вы можете обернуть useState в функцию, которая отслеживает монтирование/размонтирование, и оборачивает возвращаемую функцию setState с проверкой if.

export const MyComponent = () => {
  const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
  // ..etc.

// imported from elsewhere ////

export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
  const [val, setVal] = useStateTuple;
  const [isMounted, setIsMounted] = useState(true);
  useEffect(() => () => setIsMounted(false), []);
  return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}

Затем вы можете создать крючок useStateAsync, чтобы немного упростить.

export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
  return useUnlessUnmounted(useState(initialState));
}

Попробуйте добавить зависимости в useEffect:

  useEffect(() => {
    fetchData()
    return () => { props.spotifyAPI.cancelRequest() }
  }, [fetchData, props.spotifyAPI])

У меня такое же предупреждение, это решение сработало для меня ->

useEffect(() => {
    const unsubscribe = fetchData(); //subscribe
    return unsubscribe; //unsubscribe
}, []);

если у вас есть более одной функции выборки, то

const getData = () => {
    fetch1();
    fetch2();
    fetch3();
}

useEffect(() => {
    const unsubscribe = getData(); //subscribe
    return unsubscribe; //unsubscribe
}, []);

Простой способ

    let fetchingFunction= async()=>{
      // fetching
    }

React.useEffect(() => {
    fetchingFunction();
    return () => {
        fetchingFunction= null
    }
}, [])

есть много ответов, но я подумал, что смогу более просто продемонстрировать, как работает abort (по крайней мере, как это решило проблему для меня):

useEffect(() => {
  // get abortion variables
  let abortController = new AbortController();
  let aborted = abortController.signal.aborted; // true || false
  async function fetchResults() {
    let response = await fetch(`[WEBSITE LINK]`);
    let data = await response.json();
    aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
    if (aborted === false) {
      // All your 'set states' inside this kind of 'if' statement
      setState(data);
    }
  }
  fetchResults();
  return () => {
    abortController.abort();
  };
}, [])

Другие методы: https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8

Это правильный для проверки прерванный сигнал

sandeep.gosavi 01.11.2021 18:52

варианты = {{ filterType: "флажок" , текстовые метки: { тело: { noMatch: Загрузка? : «К сожалению, нет соответствующих данных для отображения», }, }, }}

Обычно эта проблема возникает, когда вы показываете компонент условно, например:

showModal && <Modal onClose = {toggleModal}/> 

Вы можете попробовать сделать несколько маленьких трюков в функции Modal onClose, например

setTimeout(onClose, 0)

Другие вопросы по теме