
1. はじめに
Reactの useEffect は強力ですが、「実は使わなくても良い・使ってはいけない場面」で乱用されがちです。不要な useEffect は無駄な再レンダリングやバグを引き起こします。
React公式(エフェクトは不要かもしれない)でも言及されている通り、多くは使わずにシンプルに書けます。本記事では、初心者が陥りがちなアンチパターンと正しい書き方を解説します。
2. なぜ useEffect を使いたくなってしまうのか?
「State A が変わったら State B も更新したい」という命令的な思考に陥ると、処理の連鎖として useEffect を使いたくなります。
しかし、useEffect の本来の役割は「React外のシステムと同期すること」(DOM操作、API取得、イベントリスナー登録など)です。「React内部の State から別の State を作る」ために使うのは誤りです。
3. なぜ useEffect の乱用がNGなのか?
1. 無駄な再レンダリングが発生する
useEffect 内で State を更新すると、初回描画後に直ちにもう一度再レンダリングが走り、画面のチラつきやパフォーマンス低下を招きます。
2. データフローが追えなくなる
State の連鎖更新が増えると依存関係が複雑化し、無限ループや不整合などのバグの原因になります。
4. 【コード比較】よくあるNGケースと正しい書き方
ケース1:props や state から計算できる値
NGコード:
export function UserProfile({ firstName, lastName }: { firstName: string; lastName: string }) {
const [fullName, setFullName] = useState('');
// ❌ props の変更を検知して useEffect で別の State を更新している(二重レンダリング発生)
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
return <div>{fullName}</div>;
}
上記の実装だと、firstName や lastName が変化した際に「1回目:コンポーネントの再レンダリング」→「2回目:useEffect が走って fullName を更新し、再度レンダリング」という無駄な2重レンダリングが発生してしまいます。
正しいコード:
export function UserProfile({ firstName, lastName }: { firstName: string; lastName: string }) {
// ⭕ レンダリング時に直接計算するだけ(State も useEffect も不要)
const fullName = `${firstName} ${lastName}`;
return <div>{fullName}</div>;
}
💡 計算コストが非常に高い処理(巨大配列のフィルタなど)の場合は useMemo を検討します。
ケース2:ユーザー操作(イベント)を起点とする処理
NGコード:
export function PurchaseButton({ productId }: { productId: string }) {
const [isPending, setIsPending] = useState(false);
// ❌ State の変化をトリガーにして API 送信を行っている
useEffect(() => {
if (isPending) {
buyProduct(productId).then(() => {
setIsPending(false);
alert('購入完了!');
});
}
}, [isPending, productId]);
return <button onClick={() => setIsPending(true)}>購入する</button>;
}
正しいコード:
export function PurchaseButton({ productId }: { productId: string }) {
const [isPending, setIsPending] = useState(false);
// ⭕ イベントハンドラ内で直接処理を完結させる
const handleClick = async () => {
setIsPending(true);
try {
await buyProduct(productId);
alert('購入完了!');
} finally {
setIsPending(false);
}
};
return <button onClick={handleClick} disabled={isPending}>購入する</button>;
}
このように実装することで、ユーザーが何をした時に何が起きるのかが handleClick の中に閉じるため、データの流れと処理の実行タイミングが明快になります。useEffect を介さないため、意図しないタイミングでの副作用の実行も防げます。
5. まとめ:useEffect を書く前の判断基準
useEffect を書く前に、以下を確認しましょう。
- レンダリング中に計算できないか?
既存の State/Props から作れる値は、レンダリング時の変数として直接計算する。 - イベントハンドラ内で実行できないか?
クリック等の操作をきっかけとする処理は、handleClickなどの内部に直接書く。 - 本当に外部システムとの同期か?
DOM操作や API 取得など、React外と関わる場合のみuseEffectを使う。
不要な useEffect を減らし、シンプルで堅牢なコードを目指しましょう。