ReactのuseEffectで無限ループを防ぐ方法をやさしく解説!初心者向け
生徒
「先生、ReactでuseEffectを使ったら、画面が止まらなくなりました。どうしてでしょう?」
先生
「それは無限ループが発生している可能性があります。useEffectは依存配列の値が変わるたびに処理を繰り返します。」
生徒
「依存配列って何ですか?」
先生
「依存配列は、useEffectの処理を実行する条件となる値のリストです。ここに指定した値が変わると、useEffectの中の処理が再実行されます。」
生徒
「なるほど。でもどうやって無限ループを防ぐんですか?」
先生
「依存配列を正しく設定することと、状態更新のタイミングに注意することで防げます。具体的に例を見てみましょう。」
1. useEffectで無限ループが発生する理由
ReactのuseEffectは、画面が描画されたあとに「追加でやってほしい処理(副作用)」を実行するための仕組みです。ここで注意したいのが、useEffectの中でsetState(useStateの更新)を行うと、状態が変わったことで再レンダーが起き、条件によってはuseEffectがもう一度動いてしまう点です。さらに、useEffectの中で毎回状態を更新していると、レンダー → useEffect → 状態更新 → レンダー…という流れが止まらず、useEffectの無限ループになります。特に「毎回setStateする」「毎回新しい値を作って入れる」ような書き方は、初心者がつまずきやすいポイントです。
import React, { useState, useEffect } from "react";
function App() {
const [message, setMessage] = useState("読み込み中...");
useEffect(() => {
// 何も条件がない状態更新は、繰り返し実行されやすい
setMessage("データを読み込みました");
});
return <p>{message}</p>;
}
export default App;
2. 依存配列を使って無限ループを防ぐ
依存配列とは、useEffectの第2引数に渡す角カッコ[]の中身のことです。ここに値を書くと、「その値が変わったときだけuseEffectを動かしてね」という合図になります。逆に、依存配列を書かないまま状態更新(setState)をすると、再レンダーのたびにuseEffectが実行されやすくなり、結果として無限ループにつながります。初心者のうちは、まず「useEffectは毎回動く可能性がある」「だから条件(依存配列)を付けて回数を絞る」と覚えるとスッキリします。
たとえば、ボタンを押したときだけ数が増えるような画面なら、依存配列にcountを入れておくことで、countが変わったタイミングだけ処理が走ります。これだけでも「知らないうちに何回も実行される」事故をかなり減らせます。
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
// countが変わったときだけ、ここが実行される
console.log("カウントが変わりました:", count);
}, [count]);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>カウントを増やす</button>
</div>
);
}
export default App;
3. 空の依存配列で初回のみ実行
依存配列を空([])にすると、useEffectはコンポーネントが画面に表示された「最初の1回だけ」実行されます。これは「ページを開いたときに一度だけ行いたい処理」にとても向いています。たとえば、最初のあいさつ文を表示したり、初期データを読み込んだりする場面です。依存配列が空であれば、その後に状態が何度変わってもuseEffectは再実行されないため、setStateを書いても無限ループになりません。初心者の方は「何も指定しなければ毎回動く」「空配列なら最初だけ動く」と覚えると混乱しにくくなります。
import React, { useState, useEffect } from "react";
function App() {
const [message, setMessage] = useState("");
useEffect(() => {
// 画面が最初に表示されたときだけ実行される
setMessage("こんにちは!");
}, []); // 空の依存配列
return <p>{message}</p>;
}
export default App;
4. 状態更新の条件を工夫して無限ループを回避
無限ループを防ぐもう一つの方法は、状態更新の前に条件を入れることです。これにより、本当に必要な場合だけ状態を更新できます。
useEffect(() => {
if (count < 5) { // 条件を追加
setCount(count + 1);
}
}, [count]);
5. useEffectで関数やオブジェクトを依存にする場合の注意
配列やオブジェクト、関数などを依存配列に直接入れると、Reactは毎回新しい参照と判断して再実行することがあります。無限ループを防ぐには、useCallbackやuseMemoを使って参照を安定させると安全です。
import React, { useState, useEffect, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const logCount = useCallback(() => {
console.log("カウントは:", count);
}, [count]);
useEffect(() => {
logCount();
}, [logCount]); // 関数の参照を安定させて依存
return (
カウント: {count}
);
}
export default App;
6. 無限ループになりやすいパターンを知ろう
代表的な無限ループパターンは、useEffect内でsetStateを無条件に実行するケースや、依存配列を指定せずに状態更新を行うケースです。これらは副作用と状態更新の関係を正しく理解すれば防げます。小さな条件分岐や依存配列の設定で、無限ループを安全に回避できます。
7. デバッグのポイント
無限ループが発生した場合は、useEffectの中でconsole.logを使ってどの値が何回変わっているか確認します。依存配列に指定した値や関数が毎回新しい参照になっていないかもチェックすると原因を特定しやすくなります。
8. 初心者でも意識しておきたいコツ
- 状態を更新する処理は本当に必要な場合だけ行う
- 依存配列を正しく設定して再実行を制御する
- 関数やオブジェクトを依存にする場合はuseCallbackやuseMemoを使う
- 無限ループになったらconsole.logで実行回数や値を確認する
まとめ
useEffectと無限ループの仕組みを振り返ろう
この記事では、ReactのuseEffectでなぜ無限ループが発生するのか、その原因と対策を段階的に確認してきました。useEffectはコンポーネントが描画されたあとに処理を実行するための便利な仕組みですが、状態更新と組み合わせることで思わぬ挙動を引き起こすことがあります。特に、useEffectの中でsetStateを無条件に実行すると、再レンダーが発生し、その結果として再びuseEffectが実行される流れが止まらなくなります。この仕組みを理解していないと、画面が固まったり、動作が極端に重くなったりする原因になります。
重要なポイントは、useEffectが「いつ」「どんな条件で」実行されるのかを正しく把握することです。依存配列を指定しない場合、useEffectは再レンダーのたびに実行されます。一方で、依存配列を設定すれば、その中の値が変わったときだけ実行されるようになり、不要な処理の繰り返しを防ぐことができます。さらに、空の依存配列を使えば、画面が最初に表示されたときの一度だけ実行されるため、初期処理やデータ取得などにも安心して利用できます。
状態更新と依存配列の関係を整理する
useEffectで無限ループを防ぐためには、状態更新の設計も非常に大切です。毎回同じ値をセットしていたり、条件を付けずに状態を変更していると、Reactは「状態が変わった」と判断し、再レンダーを繰り返します。そのため、状態更新の前に条件分岐を入れることで、本当に必要なタイミングだけ状態を変更するようにすると安全です。こうした工夫は、初心者が最初につまずきやすいポイントでもありますが、一度理解するとReactの動きがぐっと分かりやすくなります。
useEffect(() => {
if (count < 3) {
setCount(count + 1);
}
}, [count]);
このように条件を入れることで、特定の回数や状態に達したら処理を止めることができ、無限ループを確実に回避できます。useEffectは強力な反面、状態更新との組み合わせには常に注意が必要だということを覚えておきましょう。
関数やオブジェクトを扱うときの注意点
記事の後半では、useEffectの依存配列に関数やオブジェクトを指定する場合の注意点についても触れました。Reactでは、関数やオブジェクトは再レンダーのたびに新しい参照として扱われることがあります。その結果、見た目は同じ処理でも「値が変わった」と判断され、useEffectが何度も実行されてしまうケースがあります。この問題を防ぐために、useCallbackやuseMemoを使って参照を安定させる方法を学びました。
初心者のうちは少し難しく感じるかもしれませんが、「依存配列に入れるものは、本当に変化を監視したいものだけにする」という考え方を持つだけでも、無限ループのリスクは大きく下げられます。console.logを使ったデバッグとあわせて、実行回数や値の変化を確認する習慣を身につけることも大切です。
生徒
「useEffectって便利だけど、何も考えずに使うと無限ループになる理由がよく分かりました。特に、setStateを毎回実行してしまうのが危ないんですね。」
先生
「その通りです。useEffectは状態と強く結びついているので、依存配列と状態更新の関係を意識することがとても大切です。まずは空の依存配列や、必要な値だけを指定するところから慣れていきましょう。」
生徒
「条件を付けて状態を更新すれば、無限ループを止められるのも理解できました。console.logで確認するのも、原因を探すのに役立ちそうですね。」
先生
「とても良い視点です。Reactでは、なぜ再レンダーされたのかを考える癖をつけると、バグに強くなります。useEffectの動きが分かれば、実務でも安心してコードを書けるようになりますよ。」