React で「Too many re-renders」が解消できない(無限レンダリングループ)
「Too many re-renders. React limits the number of renders to prevent an infinite loop」は、レンダリング中に state を無条件で更新しているのが原因。
イベントに関数を即時実行で渡していないかをまず疑う。
公開: 更新:
要約
Too many re-renders. React limits the number of renders to prevent an infinite loop. は、レンダリングの最中に state を無条件で更新しているサインです。
更新がレンダーを呼び、そのレンダーがまた更新を呼ぶ――この往復が止まらず、React が安全装置として打ち切っています。
もっとも多いのは、イベントハンドラの渡し方ミスです。
実行例
レンダー中に条件なしで setState を呼ぶコンポーネントは、サーバー側のレンダリングでも Too many re-renders. React limits the number of renders to prevent an infinite loop. で停止し、スタックの先頭が dispatchAction になる。
派生値をレンダー中に計算する形と、更新前に値を比べてから呼ぶ形は、いずれも終了コード 0 で HTML を出力する。
$ node bad.js
/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6995
throw fatalError;
^
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
at dispatchAction (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5169:11)
at App (/tmp/tmp.OXk7o3kNMB/bad.js:7:3)
at finishHooks (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:4964:16)
at renderWithHooks (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5663:10)
at renderIndeterminateComponent (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5736:15)
at renderElement (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5961:7)
at renderNodeDestructiveImpl (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6119:11)
at renderNodeDestructive (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6091:14)
at retryTask (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6543:5)
at performWork (/tmp/tmp.OXk7o3kNMB/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6591:7)
Node.js v20.20.2
終了コード: 1$ node solve_derive.js
<p>0</p>
終了コード: 0$ node solve_guard.js
<p>x</p>
終了コード: 0— 2026-08-30 時点の出力
検証環境
- 検証日
- 実行環境
node:20Debian GNU/Linux 12 (bookworm)- バージョン
- Node.js 20.20.2
- npm 10.8.2
- Python 3.11.2
- Git 2.39.5
この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。
よくある原因
onClick={handleClick()}のように関数を即時実行で渡している。
返り値(多くは undefined)がハンドラになり、レンダーのたびにhandleClickが走る。- コンポーネント本体で条件なしに
setStateを呼んでいる。 - 表示用の派生値を作るためにレンダー中で
setStateしている。 useEffectの依存配列を付け忘れ、毎レンダーで state を更新している。
解決策
1. ハンドラは「関数参照」で渡す
呼び出しの丸括弧を外し、引数が必要ならアロー関数で包みます。
// NG: レンダー中に実行されてしまう
<button onClick={handleClick()}>送信</button>
// OK: 関数そのものを渡す
<button onClick={handleClick}>送信</button>
// OK: 引数つきはアローで包む
<button onClick={() => handleClick(id)}>送信</button>2. レンダー中に setState しない
状態更新はイベントハンドラか useEffect の中だけで行います。
レンダー本体は「描く」だけに保ちます。
3. 派生値は計算で求める
ほかの state から導ける値は、新しい state にせず毎回計算します。
重いときだけ useMemo を挟みます。
const fullName = `${first} ${last}`; // setState しないどうしてもレンダー中に更新が要る稀なケースは、if (prev !== next) のように条件で囲み、ループを断ち切ってください。