【React】「Too many re-renders」エラーの原因と解決策を徹底解説!無限ループを止める正しいコードの書き方

React

Reactでコンポーネントを開発しているとき、画面が真っ白になり、コンソールに以下のようなエラーが表示されて頭を抱えたことはありませんか?

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. (エラー:再レンダリングが多すぎます。Reactは無限ループを防ぐためにレンダリング数を制限しています。)

このエラーは、文字通りコンポーネントの再レンダリング(再描画)が終わりなく繰り返される「無限ループ」が発生していることを示しています。Reactが親切にもブラウザがフリーズする前に処理を強制停止してくれた状態です。

この記事では、このエラーが発生する根本的なメカニズムから、よくある3つの原因パターン、そして具体的な解決コードまでを分かりやすく解説します。

1. なぜ「Too many re-renders」が発生するのか?

原因を深く理解するために、まずはReactが画面を更新する基本的な仕組み(ライフサイクル)をおさらいしましょう。

Reactコンポーネントは、以下のいずれかの条件を満たしたときに再レンダリングされます。

  1. State(状態)が更新されたとき
  2. Props(親から渡されるデータ)が変更されたとき
  3. 親コンポーネントが再レンダリングされたとき

「Too many re-renders」が発生するのは、主に「1. Stateの更新」がトリガーとなります。 レンダリングの処理(関数コンポーネントの実行)の中でStateを更新してしまうと、以下のような悪魔のサイクルが成立します。

  1. コンポーネントがレンダリングされる
  2. レンダリングの途中でStateを更新する処理が走る
  3. Stateが更新されたので、Reactが「再レンダリングしなきゃ!」と判断する
  4. 再びコンポーネントがレンダリングされる
  5. またStateを更新する処理が走る……(以下無限ループ)

これが、エラーの正体です。

2. よくある原因パターンと解決策(コード例付き)

このエラーが発生するシチュエーションは、大きく分けて3つのパターンしかありません。自分のコードがどれに当てはまるかチェックしてみましょう。

パターン①:イベントハンドラーに「関数を実行した結果」を渡している(最頻出)

もっとも多いのが、onClick などのイベントリスナーに、関数を渡すのではなく「その場で実行した結果」を書いてしまっているパターンです。

❌ 間違ったコードの例

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  // ✕ ボタンがクリックされた時ではなく、レンダリング時に実行されてしまう
  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={setCount(count + 1)}>加算</button>
    </div>
  );
}

💡 原因の解説

onClick={setCount(count + 1)} と書くと、Reactがこのボタンを描画する(レンダリングする)瞬間に、setCount が実行されてしまいます。 すると画面を表示する前にStateが変わり、すぐさま再レンダリングが走り、また実行され……となり、一瞬で無限ループが完成します。

⭕ 正しい解決コード

イベントハンドラーには、関数の実行結果ではなく「関数そのもの(またはアロー関数)」を渡す必要があります。

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  // 〇 アロー関数で包むことで、クリックされた時だけ実行されるようにする
  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>加算</button>
    </div>
  );
}

引数がない場合は、あらかじめ定義した関数名をそのまま渡す形でもOKです(例: onClick={handleClick})。

パターン②:コンポーネントの直下(トップレベル)でStateを更新している

関数の外や、イベントハンドラーの中ではなく、コンポーネントの処理の途中で無条件に setState を実行しているパターンです。

❌ 間違ったコードの例

import { useState } from 'react';

export default function UserProfile({ isLoggedIn }) {
  const [status, setStatus] = useState('guest');

  // ✕ レンダリングのたびに条件評価され、高頻度でStateが更新される
  if (isLoggedIn) {
    setStatus('member'); 
  }

  return <div>ステータス: {status}</div>;
}

💡 原因の解説

このコンポーネントが実行されるたびに、if (isLoggedIn) の評価が行われ、setStatus('member') が呼び出されます。Stateが変わるとコンポーネントは最初から再実行されるため、再び if 文に捕まり、無限ループになります。

⭕ 正しい解決コード

初期値の段階で条件分岐させるか、あるいは「特定のデータ(Propsなど)が変わったときだけStateを同期する」ために useEffect を使用します。

import { useState, useEffect } from 'react';

export default function UserProfile({ isLoggedIn }) {
  const [status, setStatus] = useState('guest');

  // 〇 useEffectを使い、isLoggedInが変更された時だけ実行する
  useEffect(() => {
    if (isLoggedIn) {
      setStatus('member');
    } else {
      setStatus('guest');
    }
  }, [isLoggedIn]); // 依存配列に監視したい変数を指定

  return <div>ステータス: {status}</div>;
}

※なお、PropsからStateを単純に同期させるだけであれば、Stateを作らずに const status = isLoggedIn ? 'member' : 'guest'; と直接変数として計算する方が、Reactの本質的によりクリーンな設計になります。

パターン③:useEffectの依存配列(Dependency Array)の設定ミス

useEffect を使っているのに無限ループが起きる場合、第二引数である「依存配列」の設定に問題があります。

❌ 間違ったコードの例

import { useState, useEffect } from 'react';

export default function DataFetcher() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // 擬似的なデータフェッチ処理
    const fetchedData = [{ id: 1, name: '商品A' }];
    
    // ✕ 自身のStateを更新している
    setData(fetchedData);
  }, [data]); // ✕ 依存配列に更新対象の『data』を入れている
  
  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

💡 原因の解説

useEffect は、「依存配列([] の中身)に変化があったとき」に実行されます。 上記のコードでは、useEffect の中で setData を行っているにもかかわらず、依存配列に data 自体を指定しています。

  1. data が変わる ➔ 2. useEffect が動く ➔ 3. setDatadata が変わる ➔ 4. 依存配列が変化を検知して 1 に戻る、というループに陥ります。

⭕ 正しい解決コード

コンポーネントのマウント(初回表示)時に1回だけ実行したい場合は、依存配列を空の配列 [] にします。

import { useState, useEffect } from 'react';

export default function DataFetcher() {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchedData = [{ id: 1, name: '商品A' }];
    setData(fetchedData);
  }, []); // 〇 空の配列にすることで、初回レンダリング時のみ実行される

  return (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

3. エラーを未然に防ぐためのチェックリスト

今後「Too many re-renders」で開発の手を止めないために、以下のポイントを意識してコードを書きましょう。

  • [ ] onClickonChange に渡す関数に () がついて、その場で実行形になっていないか?
  • [ ] 引数付きの関数をイベントに渡すときは、() => myFunction(arg) のようにアロー関数でラップしているか?
  • [ ] コンポーネントの関数直下(トップレベル)で setSomething() を呼び出していないか?
  • [ ] useEffect の中で更新しているStateを、そのまま依存配列(第二引数)に含めてしまっていないか?

まとめ

Reactの「Too many re-renders」エラーは、一見すると深刻に見えますが、原因は「State更新タイミングのコントロールミス」が100%です。

「どこでStateが書き換わり、どのタイミングでレンダリングが走るのか」というデータの流れを意識することで、驚くほど簡単に解決・予防できるようになります。バグを恐れず、クリーンで高速なReactアプリケーションを構築していきましょう!

コメント

タイトルとURLをコピーしました