Loading
  • LIGHT

  • DARK

ROUTE

ルートゼロの
アクティビティ

React useEffect無限ループ5大原因と対策を徹底解説【参照の同一性も図解】

3

React useEffectの無限ループ、なぜ?5つの原因と対策まとめ

useEffectの無限ループ、一度はハマったことありませんか?
私たちWebエンジニアにとって、Reactの開発現場でuseEffectが原因の謎の再レンダリング――「気付いたら画面が固まる」「コンソールがエラーで埋まる」現象は“お約束”とも言える落とし穴です。
その場しのぎの// eslint-disable-lineで乗り切っても、また同じバグが顔を出す…。
そんな“技術的モヤモヤ”、一緒に論理的に解消しましょう。

(Reactの基本的なフックの違いについては『【保存版】ReactのuseStateとuseEffectの違いとは?初心者が実務で迷わない使い分け完全ガイド』をご参照ください)


なぜ無限ループ? 根本原因は「参照の同一性」

ReactuseEffectが「なぜか毎回走る」原因、キーワードは「参照の同一性」です。

  • プリミティブ型string, number など)は“値”で比較
  • オブジェクト型Object, Array, Function)は“参照(アドレス)”で比較

useEffect(() => { }, [obj]); のようにオブジェクトや関数を依存配列に入れると、毎レンダリングごとに「新しい参照」扱いとなり、無限ループの原因に。

  • コンポーネントが再レンダリング
  • 毎回「新しい参照」と判断される
  • useEffectが再実行
  • state更新がトリガー
  • 1.に戻る…
    この悪循環=「無限ループ」です。

(Reactの依存配列や再レンダリングの仕組みについては『【保存版】ReactのuseStateとuseEffectの違いとは?初心者が実務で迷わない使い分け完全ガイド』も参考になります)

用語解説:React
Facebookが開発したUI構築のためのJavaScriptライブラリ。コンポーネント単位で効率的にWebアプリを開発できる。

用語解説:useEffect
Reactの関数コンポーネントで副作用(データ取得やDOM操作など)を扱うためのフック。依存配列によって実行タイミングを制御できる。

用語解説:依存配列
useEffectの第2引数。ここに指定した値が変化したときだけeffectが再実行される仕組み。

用語解説:参照の同一性
JavaScriptではオブジェクトや配列・関数は「中身」ではなく「参照(アドレス)」で比較される。新しく生成されるたびに異なる参照とみなされる。

用語解説:プリミティブ型
string, number, boolean などの基本データ型。値そのもので比較される。

用語解説:オブジェクト型
Object, Array, Function など。参照(アドレス)で比較されるため、毎回新しく生成すると異なるものと判定される。


よくある5つの無限ループパターンと解決策


1. 依存配列にオブジェクトや配列を直接指定

現象
propsやローカル定義のオブジェクト・配列をそのまま依存配列に入れると、参照が毎回変わりループ発生。

NG例

useEffect(() => {
  // optionsが毎回新しくなる
}, [options]);

対策:useMemoでメモ化

const options = useMemo(() => ({ a: 1, b: 2 }), []);
useEffect(() => {
  // optionsの参照が保たれる
}, [options]);

props渡しも、親側でuseMemoを使うのが安全策。

(JavaScriptのthisや参照の違いについては『JavaScript thisの違い・バグ事例7選|アロー関数やbindの迷わない使い分け』もご参照ください)

用語解説:useMemo
計算結果やオブジェクト・配列を「メモ化」し、依存値が変わらない限り同じ参照を再利用するReactのフック。


2. コンポーネント内で関数を定義&依存指定

現象
コンポーネント内で関数を宣言し、それを依存配列に指定。レンダリングごとに関数参照が変わりループ。

用語解説:useCallback
関数を「メモ化」し、依存値が変わらない限り同じ参照を再利用するReactのフック。無駄な再生成や再レンダリングを防ぐ。

NG例

const fetchData = () => { ... };
useEffect(() => { fetchData(); }, [fetchData]);

対策:useCallbackで関数をメモ化

const fetchData = useCallback(() => { ... }, [依存値]);
useEffect(() => { fetchData(); }, [fetchData]);

3. useEffect内でstateを更新し、依存配列にそのstateを指定

現象
setState対象を依存配列に入れ、effect内で更新 → ループ突入。

NG例

useEffect(() => {
  setCount(count + 1);
}, [count]);

対策:依存配列の見直し or “関数型”更新

useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  return () => clearInterval(timer);
}, []);

4. useEffectコールバックに直接async/await

現象 useEffect(async () => {...}, [])Promiseを返すため非推奨。

NG例

useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

対策:effect内で別途async関数を定義・呼び出し

useEffect(() => {
  const fetch = async () => {
    const data = await fetchData();
    setData(data);
  };
  fetch();
}, []);

5. 依存配列を省略している

現象
依存配列がない場合、effectは全レンダリングで毎回発火
state更新が絡むと、そのままループ

NG例

useEffect(() => {
  setCount(c => c + 1);
}); // ←依存配列なし

対策:必ず「意図した依存のみ」指定

  • 初回だけ実行:[]
  • 特定値だけ監視:[userId]

FAQ:「eslint-disable-lineでごまかし」本当に危険?


  • Q:// eslint-disable-lineで警告を消すのはアリ?
    A:非推奨。警告は依存指定漏れ=設計不備のサインです。
    useMemo関数useCallbackで“参照の同一性”を担保し、警告を無視しないことがバグ予防につながります。

    用語解説:eslint
    JavaScriptやReactのコード品質やバグを自動検出するための静的解析ツール。ルール違反時に警告やエラーを表示する。

  • Q:useMemouseCallbackの違いは?
    A:

    • useMemo:値のメモ化(例:オブジェクト、配列、計算結果)
    • useCallback:関数そのもののメモ化

    依存配列に「値」ならuseMemo、「関数」ならuseCallbackを。

    用語解説:メモ化
    計算結果や関数・オブジェクトを一時的に保存し、同じ依存値の間は再利用する最適化手法。無駄な再計算や再生成を防ぐ。


まとめ:無限ループに怯えず、論理で解決を


  • 根本原因は「参照の同一性」
  • 主な解決策useMemouseCallbackで参照を固定
  • 依存配列は設計の要。「警告を無視しない」習慣がバグを防ぐ

まずは手元のコードで、依存配列の扱い・メモ化の使い分けを意識的に試してみましょう。

ぜひコードをコピペして、まずは動かしてみてください。

(Reactやフロントエンドフレームワークの選び方については『フロントエンドフレームワーク徹底比較!React・Vue・Angular違いと選び方』もご参照ください)

RANKINGranking-icon

LATEST POSTS

もっとルートゼロを知りたいなら

DISCOVER MORE