ReactのuseEffect
は、副作用を扱うためのフックです。
「副作用」とは、データの取得・DOMの操作・イベントリスナーの登録・タイマーの設定など、Reactのレンダリングの外で起こる処理のことです。
ちなみに、「Reactのレンダリングの外」とは、コンポーネントが画面に表示される過程(=レンダリング)とは直接関係のない処理のことです。
基本的な使い方
useEffect(() => {
// 副作用の処理(例:APIリクエスト、console.log など)
}, []);
引数の意味
useEffect(() => {
// ① 副作用の処理を書く関数(必須)
return () => {
// ② クリーンアップ関数(任意)
};
}, [依存配列]);
-
第1引数:副作用の関数
-
第2引数(依存配列):
- 空配列
[]
→ 初回マウント時のみ実行 - 配列なし → レンダリングのたびに実行
- 配列に値あり → その値が変化したときに実行
- 空配列
典型的なユースケース
① 初回マウント時のみ実行(初期化処理)
例:ページ読み込み時にデータを取得
useEffect(() => {
fetch("/api/user").then((res) => res.json()).then(setUser);
}, []); // 空配列 → 初回だけ実行
使い道:初回だけ実行したい処理(API呼び出し・初期設定)
② 値の変化に応じて実行(依存配列あり)
例:選択したIDに応じてデータ再取得
useEffect(() => {
fetch(`/api/user/${userId}`).then((res) => res.json()).then(setUser);
}, [userId]); // userId が変わるたびに実行
使い道:フォーム入力や選択値に応じた処理、フィルターの変更など
③ タイマー処理
例:5秒後にフラグを更新
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false);
}, 5000);
return () => clearTimeout(timer); // クリーンアップ
}, []);
使い道:トースト通知の消去、遅延処理、非同期処理のシミュレートなど
④ フォーム入力のリアルタイムバリデーション
例:メールアドレスのバリデーションチェック
useEffect(() => {
if (email.includes("@")) {
setEmailValid(true);
} else {
setEmailValid(false);
}
}, [email]);
使い道:入力補助やボタンの活性化制御など
⑤ ローカルストレージとの連携
例:ブラウザの設定を維持
useEffect(() => {
const saved = localStorage.getItem("theme");
if (saved) {
setTheme(saved);
}
}, []);
useEffect(() => {
localStorage.setItem("theme", theme);
}, [theme]);
使い道:ダークモードやユーザー設定の保存・復元
よくあるミスや注意点
① 依存配列(第二引数)を省略する
useEffect(() => {
fetch("/api/data").then(setData);
}); // 依存配列なし
問題点:レンダリングごとに毎回実行されて、無限ループや通信のしすぎの原因に
正しくは:
useEffect(() => {
fetch("/api/data").then(setData);
}, []); // 初回のみ実行
② 不適切な依存配列(必要な値を入れ忘れる)
useEffect(() => {
console.log(user.name); // userが変わっても再実行されない
}, []); // userが依存配列にない
問題点:
- 値は変わっているのに副作用が実行されない
- 古い値を使ってしまう
正しくは:
useEffect(() => {
console.log(user.name);
}, [user]); // userが変わったら再実行
③ 非同期関数を直接使う
useEffect(async () => {
const res = await fetch("/api");
const data = await res.json();
setData(data);
}, []); // useEffectにasyncは直接書けない
問題点: useEffect
は async
を返すとエラーになる
useEffect の返り値は、クリーンアップ関数(または undefined)でなければならないです。
正しくは:
useEffect(() => {
const fetchData = async () => {
const res = await fetch("/api");
const data = await res.json();
setData(data);
};
fetchData();
}, []);
④ クリーンアップを忘れる
useEffect(() => {
window.addEventListener("resize", handleResize);
}, []); // クリーンアップがない
問題点:
- イベントがどんどん登録されて「ゴーストイベント」になる
- メモリリークの原因にもなる
正しくは:
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize); // ✅ クリーンアップ
};
}, []);
⑤ 状態を連続で更新 → 無限ループ
useEffect(() => {
setCount(count + 1); // useEffectで状態を変えると再実行され続ける
}, [count]);
問題点:
-
count
を変えるたびにuseEffect
が実行される -
setCount
→ countが変わる → useEffect再実行 → 無限ループ
対策:
- 条件をつける、または
setTimeout
などで制御する -
useRef
を使って初回だけスキップするなど
useEffectを使わない方がよいケース
1. 状態に応じた処理が「そのままレンダリングに使える」とき
NG例(useEffectでバリデーションをしている)
const [email, setEmail] = useState("");
const [isValid, setIsValid] = useState(false);
useEffect(() => {
setIsValid(email.includes("@"));
}, [email]);
よりシンプルな書き方(useEffect不要)
const isValid = email.includes("@");
状態 email
から導かれる値 isValid
は、
計算で求まるので useEffect ではなく「変数」または useMemo
で十分です。
2. 状態を更新するだけのロジックをuseEffectに書いてしまう
NG例(ボタンを押したらカウントを上げる → useEffectで状態更新)
const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) setCount((prev) => prev + 1);
}, [clicked]);
より適切な書き方(クリック時に直接更新)
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prev) => prev + 1);
};
「ボタンを押したら実行」→ これはイベントハンドラで処理すべきで、副作用ではない。
3. ユーザー入力の状態をリアルタイムに保持するだけのとき
NG例(入力を状態に反映するだけなのに useEffect)
const [input, setInput] = useState("");
const [text, setText] = useState("");
useEffect(() => {
setText(input);
}, [input]);
正しくは text
状態そのものが不要
const [text, setText] = useState(""); // 入力と表示が同じならこれ1つでOK
「ある状態が別の状態にコピーされるだけ」なら そもそも状態を分けない方が良い。
4. 他のReactフックで代替できるとき
やりたいこと | 適した手法 | なぜ useEffect は不要? |
---|---|---|
計算コストの高い処理の結果を保持 | useMemo |
レンダリング時に再計算されない |
イベント内の関数を再利用 | useCallback |
無駄な再生成を防げる |
状態管理が複雑 | useReducer |
状態とロジックの分離ができる |
無駄なuseEffect
が増えることのデメリット
- 可読性・保守性の低下
- テストやデバッグが困難
- 責務の分離ができなくなる
- パフォーマンス低下
seEffectを適切に使うためには
- JSXだけでできることはuseEffectに書かない
- 状態の変化によって「Reactの外側」で何かするならuseEffect
- 副作用は開始と終了をワンセットで考える
- 「これuseEffect必要?」と1回立ち止まって考える
まとめ
useEffect
は便利に見えるが「とりあえず書く」のではなく、使うべきタイミングと使わなくていい場面を見極めることが大切。
Reactの「状態の変化で自動的に再描画される」という特徴を活かしながら、useEffect
を最小限・適切に使えるようになれば、よりシンプルでバグの少ない設計が実現できる。