😽

LLM が書く React の useEffect 地獄を ESLint で撲滅する委員会

に公開

なぜ LLM は useEffect を多用するのか

Claude Code, Codex など LLM は、計算で済む値に対しても useEffect と useState を使ってしまう問題が報告されている。

import { useEffect, useState } from 'react';

// ❌ LLM が生成しがちなコード
function UserProfile({ user }) {
  const [displayName, setDisplayName] = useState('');

  useEffect(() => {
    setDisplayName(`${user.firstName} ${user.lastName}`);
  }, [user]);

  return <div>{displayName}</div>;
}

// ✅ 実際は useEffect 不要
function UserProfile({ user }) {
  const displayName = `${user.firstName} ${user.lastName}`;
  return <div>{displayName}</div>;
}

開発者コミュニティでは、「Don't overuse useState and useEffect hooks」といった指示や prompt の追加を行なっているとかなんとか...学習データの偏りや、誤用パターンがあまりにも多いのかもしれません

React 公式の見解

React 公式ドキュメント「You Might Not Need an Effect」では、useEffect が不要な12のケースが紹介されています:

  1. props/state に基づく状態更新 - 派生状態は計算で導出する
  2. 高コストな計算のキャッシュ - useMemo を使用する
  3. prop変更時の全状態リセット - key プロップを使用する
  4. prop変更時の部分的な状態調整 - レンダリング中に計算する
  5. イベントハンドラー間でのロジック共有 - 共通関数を抽出する
  6. POST リクエスト送信 - イベントハンドラーで処理する
  7. 計算チェーン - イベントハンドラー内で完結させる
  8. アプリケーション初期化 - トップレベル変数で管理する
  9. 親への状態変更通知 - イベントハンドラーで統合処理する
  10. 親へのデータ渡し - 親のイベントハンドラーで処理する
  11. 外部ストアへの購読 - useSyncExternalStore を使用する
  12. データフェッチ - フレームワークの機能やライブラリを使用する

なるほど!
しかし、全部覚えるなんて LLM により脳が溶けている私には到底できません。

ライブラリの導入

世の中には便利なライブラリがあるもんです。
eslint-plugin-react-you-might-not-need-an-effect を使えば、不要な useEffect を自動検出してくれます。

インストール

npm install --save-dev eslint-plugin-react-you-might-not-need-an-effect

設定(ESLint 9)

// eslint.config.mjs
import reactYouMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";

export default [
  reactYouMightNotNeedAnEffect.configs.recommended,
];

検出されるパターン

プラグインは10個のルールで不要な useEffect を警告します:

  1. no-derived-state - 派生状態を useEffect で計算している
  2. no-chain-state-updates - useEffect 内で状態を連鎖的に更新している
  3. no-event-handler - イベントハンドラーの代わりに useEffect を使用している
  4. no-adjust-state-on-prop-change - prop変更時に状態を部分的に調整している
  5. no-reset-all-state-on-prop-change - prop変更時に全状態をリセットしている
  6. no-pass-live-state-to-parent - 親に状態の変更を通知している
  7. no-pass-data-to-parent - 親にデータを渡している
  8. no-initialize-state - useEffect で状態を初期化している
  9. no-manage-parent - propsだけを使用するuseEffectを使っている
  10. no-empty-effect - 空の useEffect を使用している

うーん、とても便利。
しかしながら万能ではない。正当な理由で useEffect を利用しているのに警告が出る場合は eslint-disable-next-line で明示しちゃいます。これで解決。

多くの人がこのライブラリを使えば、LLM が学習して、React のベストプラクティスに則った出力をしてくれる未来が来るのでは?と妄想しています。

PornFusion について

宣伝です💁‍♀️

https://pornfusion.com/

Next.js, Supabase を活用してシコシコと開発中。登録不要で完全無料で利用できるので、ぜひ〜

Discussion