useEffectとは
「画面の変化に合わせて“副作用”を実行する仕組み」
ここまで扱ってきた useState は、「値(状態)を覚えておく仕組み」でした。useEffect は、それとは別の役割を持っています。
一言でいうと、
「コンポーネントの描画(レンダリング)のあとに、追加でやりたい処理を書く場所」
です。
例えば、こんなときに使います。
- 画面が表示されたタイミングでログを出したい
- API からデータを取得したい
- ブラウザのタイトル(
document.title)を更新したい - ローカルストレージから値を読み書きしたい
これらは、ただ JSX を返すだけではできない「副作用(サイドエフェクト)」です。
React の世界では、UI を作る関数(コンポーネント)と“副作用”を行う処理を分けるために useEffect を使います。
useEffectの基本形
書き方と「いつ実行されるか」の考え方
基本形はこの形です。
useEffect(() => {
// ここに副作用の処理を書く
}, [依存値]);
TSXそれぞれの意味を整理するとこうなります。
第一引数:() => { ... } の部分
実際に実行したい処理(関数)を書く場所
第二引数:[依存値] の部分(依存配列)
「どのタイミングでこの処理をもう一度実行するか」を React に伝える
この「依存配列」が useEffect のキモです。
ここに何を書くかで、
- 初回だけ実行
- 特定の state が変わるたびに実行
- 毎回のレンダリングごとに実行(依存配列を省略した場合)
が変わります。
初回処理(マウント時の一度だけ実行)
「ページが表示されたタイミングで一度だけ」の典型パターン
例えば、「コンポーネントが表示されたときにだけログを出したい」場合。
import { useEffect } from "react";
function Example() {
useEffect(() => {
console.log("コンポーネントがマウントされました");
}, []);
return <p>Example コンポーネントです。</p>;
}
TSXここで重要なのは、useEffect の第二引数が [](空配列)になっていることです。
意味としては、
- 初回レンダリング後に
- コールバック(
() => { ... }の中身)を一度だけ実行し - その後は再レンダリングがあっても実行しない
という指定になります。
Next.js では、このパターンは例えばこんな用途に使えます。
- 初回表示時にアクセスログを送る
- 最初にだけローカルストレージから設定を読み込む
- 初回にだけ外部 API を叩いてデータを取得する
「画面が出たタイミングで、1 回だけ何かしたい」ときは、とりあえず useEffect(() => { ... }, []) を思い出せばOKです。
状態変更との関係
特定の state が変わったときにだけ実行する
useEffect は、「依存している値が変わったとき」に再実行されます。
例えば、カウントが変わるたびに処理をしたい場合を考えましょう。
import { useEffect, useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count が変わりました:", count);
}, [count]);
return (
<main>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
</main>
);
}
TSXこの場合の流れを丁寧に追ってみます。
最初の表示count の初期値は 0。
初回レンダリング後に useEffect が実行され、「count が変わりました: 0」とログが出る。
ボタンクリックsetCount(count + 1) によって count が 1 になる。
state が変わったのでコンポーネントが再レンダリングされる。[count] と指定されているため、「count が前回と違う」と判断され、useEffect が再度実行される。
コンソールには「count が変わりました: 1」とログが出る。
さらにボタンを押すたびに、同じようにログが更新されます。
ポイントは、
useEffectの依存配列に書いた値(ここではcount)が変わると、もう一度実行される- 変わっていなければ実行されない
ということです。
「状態 → それに応じた副作用」という関係
この考え方はとても重要です。
コンポーネントの中では、
- JSX の中で「状態 → 画面の見た目」を書き
- useEffect の中で「状態 → 外部への影響(ログ、API、DOM 操作など)」を書く
という役割分担になります。
例えば、次のようなパターンが典型です。
searchQueryが変わったら、API に検索リクエストを飛ばすisDarkModeが変わったら、document.bodyにクラスを付け替えるuserIdが変わったら、そのユーザー詳細を取得する
いずれも、「ある状態が変化したら、それに応じて副作用を実行する」という構造になっています。
よくある無限ループ
「useEffect の中で state を更新 → また useEffect が走る」のパターン
初心者が useEffect で一番ハマるのが「無限ループ」です。
典型的なダメな例を先に見ておきましょう。
import { useEffect, useState } from "react";
export default function BadExample() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, [count]);
return <p>カウント: {count}</p>;
}
TSX一見、「count が変わるたびに +1 する」という意味に見えますが、実際にはこうなります。
初回レンダリングcount は 0
useEffect 実行 → setCount(1)
state 更新count が 1 になる → 再レンダリング
「count が 0 → 1 に変わった」ので、依存配列 [count] により useEffect が再度実行
useEffect 実行 → setCount(2)
state 更新count が 2 になる → 再レンダリング
また useEffect 実行 → setCount(3)
…これが永遠に続く
つまり、
- 「count が変わったら useEffect 実行」
- 「useEffect の中で count を変えている」
という自己再帰みたいな状態になってしまっています。
どうすれば防げるか
大事なのは、「依存配列に書いた値を、useEffect の中でどう扱うか」を意識することです。
無限ループになりやすいパターンの特徴は、
- 依存配列に
countが入っている - かつ
useEffectの中でsetCountを呼んでいる
という組み合わせです。
これを回避する典型的な方法は、
「本当にその state を依存にする必要があるか?」
を見直すことです。
例えば、「初回にだけ一度だけ実行したい処理」なのに [count] を入れていたりする場合は、
素直に [](空配列)にするべきです。
useEffect(() => {
// 初回だけやりたい処理
}, []); // count を依存にしない
TSXあるいは、
「何かの計算をして state に反映したい」けど、それが再レンダリングのたびに走る意味はない
「レスポンスを state に入れるだけ」で、依存のほうは userId だけでよい
など、ロジックを少し整理することで無限ループは避けられます。
もう一つのよくある誤解
依存配列を空にしないと「常に最新の値を取れない」と思って、
なんでもかんでも state を全部依存配列に入れてしまうパターンも危険です。
useEffect の中から参照する値を「全部」依存に入れようとすると、
結果的に「どれかが変わるたびに useEffect が走る」ことになり、
「その中で state を更新して → それでまた依存が変わって → ループ」という罠に入りやすくなります。
初心者のうちは、まずは次の 3 パターンの使い分けに慣れるのがおすすめです。
- 「初回だけ」 →
useEffect(() => { ... }, []) - 「この state が変わるたびに」 →
useEffect(() => { ... }, [特定の state だけ]) - 「副作用の中で安易に setState を呼ばない(本当に必要なときだけ)」
このルールを守るだけで、無限ループの 8 割くらいは防げます。
まとめと、useEffectを怖がらないために
useEffect は、コンポーネントのレンダリング後に実行される「副作用用のフック」で、ログ出力・API 呼び出し・DOM 直接操作など「UI 以外の仕事」を担当する。
第二引数の依存配列で「いつ実行するか」が決まり、[] なら「初回だけ」、[count] なら「count が変わるたび」というように、状態と処理の関係を宣言できる。
無限ループは「依存に入れた state を、useEffect の中でまた更新してしまう」ことで起きるので、依存配列と副作用内の setState の関係に気をつけることが重要になる。
次のステップとしては、
- 「初回にだけ API からダミーデータを取ってきて表示するコンポーネント」
- 「検索キーワードが変わるたびにコンソールにログを出すコンポーネント」
などを自分で書いてみると、useEffect の「タイミング」と「依存配列」の感覚がかなり掴めてきます。

