こんにちは、テックバディ運営の大下です。AI駆動開発を学べるエンジニア向けスクール「テックバディ」を運営していて、フロントエンドを学び始めた受講生からよく相談される内容をまとめてみます。
Reactを触り始めて、真っ先にぶつかる壁のひとつがuseEffectの無限ループではないでしょうか。画面が固まる、コンソールが同じログで埋め尽くされる、CPU使用率が跳ね上がる——原因が分からないままuseEffectをコメントアウトして一旦逃げた経験がある人も多いと思います。
今回は、無限ループが起きたときに上から順番に確認すれば大体解決できる、5つのチェックポイントをまとめます。
そもそもなぜ無限ループになるのか
useEffectは「依存配列(第2引数)の中身が変わったら再実行する」仕組みです。つまり無限ループは、effectの中でstateを更新し、その更新が依存配列の値を変え、また同じeffectが実行されるという循環が起きている状態です。
// これは無限ループになる
useEffect(() => {
setCount(count + 1); // stateを更新
}, [count]); // countが変わるたびにeffectが実行される
原因の構造さえ理解すれば、あとは「どこで循環が起きているか」を機械的に探すだけです。
チェックポイント1:依存配列は空か、正しく指定されているか
まず依存配列そのものを疑います。
// 空配列:マウント時に1回だけ実行される(意図通りか確認)
useEffect(() => { ... }, []);
// 依存配列なし:毎レンダリングで実行される(危険)
useEffect(() => { ... });
依存配列を書き忘れているだけ、というケースが実は一番多いです。まずここを見ましょう。
チェックポイント2:effect内でオブジェクトや配列を新規生成していないか
依存配列に入れているつもりのオブジェクトが、実は毎回新しく作られていないか確認します。
// NG:optionsが毎レンダリング新規オブジェクトになる
const options = { userId };
useEffect(() => {
fetchData(options);
}, [options]); // optionsは毎回「別物」なので毎回実行される
// OK:プリミティブ値を依存配列に入れる
useEffect(() => {
fetchData({ userId });
}, [userId]);
JavaScriptはオブジェクトを値ではなく参照で比較するため、中身が同じでも毎回「別物」と判定されてしまいます。
チェックポイント3:setStateがeffectの中で無条件に呼ばれていないか
effectの中でstateを更新すること自体は問題ありませんが、条件なしで毎回呼んでいないか確認します。
// NG:条件なしで毎回更新
useEffect(() => {
setData(fetchedData);
}, [fetchedData]);
// OK:値が変わったときだけ更新するガードを入れる
useEffect(() => {
if (data !== fetchedData) {
setData(fetchedData);
}
}, [fetchedData, data]);
チェックポイント4:親コンポーネントから渡される関数・オブジェクトpropsが毎回再生成されていないか
自分のコンポーネントではなく、親側が原因のこともよくあります。
// 親コンポーネント側
// NG:レンダリングのたびに新しい関数が渡される
<Child onUpdate={() => doSomething()} />
// OK:useCallbackでメモ化する
const handleUpdate = useCallback(() => doSomething(), []);
<Child onUpdate={handleUpdate} />
子コンポーネント側のuseEffectの依存配列にonUpdateが入っていると、親の再レンダリングのたびにeffectが再実行されてしまいます。
チェックポイント5:カスタムフックの中で同じ問題を繰り返していないか
自作のカスタムフックの中に、チェックポイント1〜4と同じ問題が隠れていないか確認します。特にAPI通信をラップしたカスタムフックは要注意です。
// カスタムフック内
function useUser(userId) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // userIdだけに絞れているか確認
return user;
}
呼び出し元では気づきにくいので、無限ループが起きたら使用しているカスタムフックの中身まで遡って見るクセをつけると原因特定が早くなります。
まとめ
無限ループの原因は突き詰めると「依存配列の値が、effectの実行によって毎回変わってしまっている」の一言に集約されます。上から5つを順番に確認すれば、大抵はどこかで循環の起点が見つかります。エラーメッセージが出ない分やっかいなバグですが、構造を理解していれば怖くありません。
AI駆動開発を実務レベルで学べるエンジニア向けスクール「テックバディ」について
「テックバディ」は、生成AIを使いこなして実務レベルの開発をやりきる力を、模擬案件を通じて身につけるエンジニア向けスクールです。「AIに書かせるだけ」で終わらせず、設計力・品質担保力・判断力まで鍛えるカリキュラムで、現場で任される仕事の幅を広げたい方をサポートしています。
