零弐壱蜂

[React 18] useEffect が2回実行されてしまう問題の対処法

React 18 では、開発モードで useEffect が 2 回実行されるようになった。これは、React の厳密モード (StrictMode) による意図的な挙動であり、潜在的なバグを検出しやすくするための変更である。

確認した環境

この現象を確認した環境は以下の通り。

  • next: v12.2.2
  • react: v18.2.0
  • react-dom: v18.2.0

解決方法

状況に応じて、以下のいずれかの方法で対応できる。

StrictModeコンポーネントを削除する

厳密モードを無効にすることで、useEffect が 2 回実行される問題を回避できる。ただし、厳密モードを無効にすることで、React の一部のデバッグ機能が利用できなくなる点に注意。

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
  // <StrictMode>
  <App />,
  // </StrictMode>
);

クリーンアップ関数を定義する

useEffect 内でクリーンアップ関数を定義し、複数回実行されても問題が起きないようにする。この方法は、データ取得やサブスクリプションの解除が必要な場合に有効。

const [results, setResults] = useState([]);
const [page, setPage] = useState(1);

useEffect(() => {
  let ignore = false;

  fetchResults(query, page).then((json) => {
    if (!ignore) {
      setResults(json);
    }
  });

  return () => {
    ignore = true;
  };
}, [query, page]);

useRefを利用する

useRef を使用して、初回の実行をスキップする方法。この方法は、開発モードでのみ問題を回避したい場合に適している。

const refFirstRef = useRef(true);

useEffect(() => {
  if (process.env.NODE_ENV === 'development') {
    if (refFirstRef.current) {
      refFirstRef.current = false;
      return;
    }
  }

  something();
}, []);

このコードでは、process.env.NODE_ENV === 'development' の条件を追加することで、プロダクションビルド時には影響が出ないようにしている。

参考