【React】なぜuseEffectの乱用はNGなのか?不要な理由と正しい使い道を解説

【React】なぜuseEffectの乱用はNGなのか?不要な理由と正しい使い道を解説

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 を書く前に、以下を確認しましょう。

  1. レンダリング中に計算できないか?
    既存の State/Props から作れる値は、レンダリング時の変数として直接計算する。
  2. イベントハンドラ内で実行できないか?
    クリック等の操作をきっかけとする処理は、handleClick などの内部に直接書く。
  3. 本当に外部システムとの同期か?
    DOM操作や API 取得など、React外と関わる場合のみ useEffect を使う。

不要な useEffect を減らし、シンプルで堅牢なコードを目指しましょう。

株式会社SPで一緒に働いてみませんか?

SPはエンジニアの成長を大切にする会社です。

ご興味ある方は一度気軽な雰囲気で、カジュアル面談はいかがでしょうか?

どのような課題を
解決したいですか?

株式会社SPでは、お客様の取り組みに寄り添いながら、
課題解決を伴走支援していきます。

まずはお気軽にこちらからお問い合わせください。

お問い合わせ・相談する(無料)