mcpField
N
no-unnecessary-effects

no-unnecessary-effects

github.com/cst2989開発3035 回インストール27 週間前に更新
リポジトリ

no-unnecessary-effects について

React 開発者向けに、useEffect の必要性を判断し、よくある場面での代替策を示すガイドです。外部システムとの同期に用途を絞ります。

主な機能

  1. 01useEffect の必要性を判断
  2. 02派生データをレンダー中に計算
  3. 03ユーザー操作をイベントハンドラーで処理
  4. 04外部サブスクリプションのクリーンアップを案内

活用シーン

  1. 01useEffect による派生状態の保存を回避
  2. 02クリックやフォーム送信の処理
  3. 03WebSocket やサードパーティウィジェットへの接続
  4. 04ブラウザー API の購読や DOM の計測

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cst2989/react-tips-skill --skill no-unnecessary-effects

インストール後、Claude Code で/no-unnecessary-effectsを入力して読み込まれたか確認します。

詳細

useEffect を書く前に

useEffect を書こうとするたびに、立ち止まってこの質問に答えてください:

これは外部システムとの同期ですか?

外部システム: WebSocket、ブラウザ API(IntersectionObserver、navigator.onLine)、サードパーティライブラリ(地図 SDK、チャートウィジェット)、DOM の測定、setInterval タイマー。

外部システムではないもの: props、state、props や state から派生した値、ユーザーイベント(クリック、フォーム送信)。

答えが「いいえ」なら、その effect は書かないでください。以下の決定木を使って、適切な代替手段を見つけてください。

決定木

effect を書く前に、各ケースを順番に確認してください:

1. データを変換または派生させようとしていませんか?

インラインで計算してください。state も effect も不要です。

// NEVER do this
const [filtered, setFiltered] = useState([]);
useEffect(() => {
  setFiltered(data.filter(item => item.active));
}, [data]);

// DO this
const filtered = data.filter(item => item.active);

// If genuinely expensive and not using React Compiler:
const filtered = useMemo(() => data.filter(item => item.active), [data]);
2. ユーザーイベントに応答しようとしていませんか?

ロジックはイベントハンドラーに置いてください。effect はレンダーに応答するものであり、ユーザーの操作に応答するものではありません。

// NEVER do this
useEffect(() => {
  if (submitted) {
    performSearch(query);
    setSubmitted(false);
  }
}, [submitted, query]);

// DO this
function handleSubmit(e: FormEvent) {
  e.preventDefault();
  performSearch(query);
}
3. props が変化したときに state をリセットしようとしていませんか?

key prop を使って、React にコンポーネントをアンマウントして新しい state で再マウントさせてください。

// NEVER do this
useEffect(() => {
  setComment('');
}, [userId]);

// DO this — in the parent
<UserProfile key={userId} userId={userId} />
4. データを取得しようとしていませんか?

TanStack Query(React Query)または同様のライブラリを使用してください。どうしても useEffect を使わなければならない場合は、古いレスポンスを無視するためのクリーンアップを必ず追加してください。

// Preferred
const { data, isLoading, error } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

// If useEffect is unavoidable, always handle race conditions
useEffect(() => {
  let ignore = false;
  fetchUser(userId).then(data => {
    if (!ignore) setUser(data);
  });
  return () => { ignore = true; };
}, [userId]);
5. 親コンポーネントに通知しようとしていませんか?

setState と並べて、イベントハンドラー内で親のコールバックを直接呼び出してください。React は両方の更新を 1 回のレンダーにバッチ処理します。

// NEVER do this
useEffect(() => {
  onChange(isOn);
}, [isOn, onChange]);

// DO this
function handleClick() {
  const next = !isOn;
  setIsOn(next);
  onChange(next);
}
6. 複数の effect を連鎖させようとしていませんか?

そのカスケードを単一のイベントハンドラーに移動してください。レンダー中に派生できるものは派生させてください。

// NEVER do this — three effects, three render passes
useEffect(() => { setCity(''); }, [country]);
useEffect(() => { setDistrict(''); }, [city]);
useEffect(() => { setShippingCost(calculate(country, city, district)); }, [country, city, district]);

// DO this
function handleCountryChange(newCountry: string) {
  setCountry(newCountry);
  setCity('');
  setDistrict('');
}

const shippingCost = country && city && district
  ? calculateShipping(country, city, district)
  : 0;
7. 外部ストアを購読しようとしていませんか?

useEffect で手動でリスナーを配線する代わりに、useSyncExternalStore を使用してください。

// NEVER do this
useEffect(() => {
  const handler = () => setIsOnline(navigator.onLine);
  window.addEventListener('online', handler);
  window.addEventListener('offline', handler);
  return () => {
    window.removeEventListener('online', handler);
    window.removeEventListener('offline', handler);
  };
}, []);

// DO this
function subscribe(callback: () => void) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine, () => true);

useEffect が正しい場合

上記のどのケースにも当てはまらず、「これは外部システムですか?」という質問への答えが本当に はい であるなら、useEffect は適切なツールです。例:

  • WebSocket 接続(マウント時に開き、アンマウント時に閉じる)
  • サードパーティウィジェットの初期化(地図 SDK、リッチテキストエディター)
  • DOM の測定(描画前は useLayoutEffect、描画後は useEffect)
  • クリーンアップを伴うブラウザ API の購読(IntersectionObserver、ResizeObserver)

有効な effect を書くときは:

  • 関数に名前を付ける と可読性が上がります: useEffect(function connectToChat() { ... })
  • 購読または接続を行う場合は 必ずクリーンアップ関数を返す
  • effect が読み取る すべての依存関係を列挙する
  • 視覚的なちらつきを避けるために DOM を測定する場合は useLayoutEffect を使用する

ルール

  • useEffect(() => setSomething(derivedValue), [dep]) は絶対に書かない — インラインで計算してください
  • クリック/送信/変更イベントに応答するために useEffect を絶対に使わない
  • まず key prop を検討せずに、prop の変化で state をリセットするために useEffect を絶対に使わない
  • ある effect が state を設定し、それが別の effect をトリガーするような effect の連鎖は絶対に行わない
  • 外部システムを購読する場合は必ずクリーンアップ関数を追加する
  • 可読性のために必ず effect 関数に名前を付ける

コメント