React報錯之無法在未掛載的組件上執行React狀態更新

正文從這開始~

總覽

為了解決"Warning: Can't perform a React state update on an unmounted component" ,可以在你的useEffect鉤子中聲明一個isMounted布爾值,用來跟蹤組件是否被安裝。一個組件的狀態只有在該組件被掛載時才會被更新。

import {useState, useEffect} from 'react';

const App = () => {
  const [state, setState] = useState('');

  useEffect(() => {
    // 👇️ set isMounted to true
    let isMounted = true;

    async function fetchData() {
      const result = await Promise.resolve(['hello', 'world']);

      // 👇️ only update state if component is mounted
      if (isMounted) {
        setState(result);
      }
    }

    fetchData();

    return () => {
      // 👇️ when component unmounts, set isMounted to false
      isMounted = false;
    };
  }, []);

  return (
    <div>
      <h2>State: {JSON.stringify(state)}</h2>
    </div>
  );
};

export default App;

當我們試圖更新一個未掛載的組件的狀態時,會出現”無法在未掛載的組件上執行React狀態更新”的警告。

isMounted

擺脫該警告的直截了當的方式是,在useEffect鉤子中使用isMounted布爾值來跟蹤組件是否被掛載。

useEffect中,我們初始化isMounted布爾值為true

我們的fetchData 函數執行一些異步的任務,最常見的是一個API請求,並根據響應來更新狀態。

然而,需要注意的是,我們只有當isMounted變量被設置為true時,才會更新狀態。

async function fetchData() {
  const result = await Promise.resolve(['hello', 'world']);

  // 👇️ only update state if component is mounted
  if (isMounted) {
    setState(result);
  }
}

這可以幫助我們避免警告,因為如果組件沒有掛載,我們就不會更新狀態。

當組件卸載時,從useEffect鉤子返回的函數會被調用。

return () => {
  // 👇️ when component unmounts, set isMounted to false
  isMounted = false;
};

我們設置isMounted變量為false,表示該組件不再掛載。如果fetchData函數在組件卸載時被調用,if代碼塊不會執行是因為isMounted設置為false

async function fetchData() {
  const result = await Promise.resolve(['hello', 'world']);

  // 👇️ only update state if component is mounted
  if (isMounted) {
    setState(result);
  }
}

提取

如果經常這樣做,可以將邏輯提取到可重用的鉤子中。

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

// 👇️ extract logic into reusable hook
function useIsMounted() {
  const isMounted = useRef(false);

  useEffect(() => {
    isMounted.current = true;

    return () => {
      isMounted.current = false;
    };
  });

  return isMounted;
}

const App = () => {
  const [state, setState] = useState('');

  // 👇️ use hook
  const isMountedRef = useIsMounted();

  useEffect(() => {
    async function fetchData() {
      const result = await Promise.resolve(['hello', 'world']);

      // 👇️ only update state if component is mounted
      if (isMountedRef.current) {
        setState(result);
      }
    }

    fetchData();
  }, [isMountedRef]);

  return (
    <div>
      <h2>State: {JSON.stringify(state)}</h2>
    </div>
  );
};

export default App;

useRef()鉤子可以傳遞一個初始值作為參數。該鉤子返回一個可變的ref對象,其.current屬性被初始化為傳遞的參數。

我們在useIsMounted鉤子中跟蹤組件是否被掛載,就像我們直接在組件的useEffect鉤子中做的那樣。

需要注意的是,在fetchData函數中,我們必須檢查isMountedRef.current 的值,因為ref上的current屬性是ref的實際值。