React で「Maximum update depth exceeded」が解決できない
レンダリングのたびに state 更新が走ると再レンダリングが連鎖し Maximum update depth exceeded になる。
ハンドラの即時呼び出し、render 中の setState、useEffect の依存設計を見直す。
#react#setstate#render#infinite-loop#hooks
要約
Maximum update depth exceeded は、レンダリングのたびに state 更新が発生し、再レンダリングが無限に連鎖 したときに React が止める警告。
原因はほぼ 3 つ — イベントハンドラの即時呼び出し、render 中の setState、useEffect の依存設計ミス。
どれも「更新を呼ぶタイミング」の問題に集約される。
よくある原因
- ハンドラの即時呼び出し:
onClick={setOpen(true)}のように、関数参照ではなく実行結果を渡している - render 中の setState: コンポーネント本体で直接
setStateを呼び、毎レンダリングで更新が走る - useEffect の自己再起: Effect 内で更新した state を依存配列に入れ、更新 → 再実行を繰り返す
- 参照の作り直し: 親が毎回新しい関数 / オブジェクトを子に渡し、子の更新が連鎖する
解決策
1. ハンドラは渡す、呼ばない
// NG: レンダリング毎に実行され setState が走る
<button onClick={setOpen(true)}>open</button>
// OK: 関数参照を渡す
<button onClick={() => setOpen(true)}>open</button>イベントに渡すのは「呼び出し方」であって「呼び出した結果」ではない。
考え方は 公式の Responding to Events を参照。
2. render 中に setState しない
// NG: 毎レンダリングで呼ばれ無限ループ
function App() {
const [n, setN] = useState(0);
setN(n + 1);
return <p>{n}</p>;
}初期値は useState の引数(必要なら遅延初期化)で与え、副作用は useEffect に閉じ込める。setState の挙動は useState 公式リファレンス。
3. useEffect の依存を切る
// NG: count を依存に入れると更新で再実行が連鎖
useEffect(() => {
setCount(count + 1);
}, [count]);
// OK: 関数型にして count を依存から外す
useEffect(() => {
setCount(c => c + 1);
}, []);4. 子へ渡す参照を安定化する
const handleClick = useCallback(() => doIt(id), [id]);毎レンダリングで新しい関数を作って子に渡すと、子側の更新が連鎖することがある。useCallback / useMemo で参照を固定し、不要な再レンダリングの連鎖を断つ。