mcpField
N
no-use-effect

no-use-effect

github.com/factory-ai開発1171.0k 回インストール3 日前に更新
リポジトリ

no-use-effect について

React のコーディングエージェント向けに、useEffect を直接呼び出さず、目的に応じた代替手段を選ぶスキルです。派生状態、データ取得、イベント処理、マウント時の同期、状態リセットを扱います。

主な機能

  1. 01状態同期の代わりに派生値を使用
  2. 02データ取得ライブラリの利用指針
  3. 03ユーザー操作にイベントハンドラーを使用
  4. 04マウント時の同期に useMountEffect
  5. 05key による状態リセット

活用シーン

  1. 01新しい React コンポーネントの作成
  2. 02既存の useEffect 呼び出しのリファクタリング
  3. 03useEffect を追加するプルリクエストのレビュー

インストール

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

$npx skills add https://github.com/factory-ai/factory-plugins --skill no-use-effect

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

詳細

useEffect は使用しない

useEffect を直接呼び出さないでください。代わりに、派生状態、イベントハンドラー、データ取得ライブラリ、または useMountEffect を使用してください。

クイックリファレンス

useEffect の代わりに...使用するもの
他の状態/props から状態を導出するインライン計算(ルール 1)
データを取得するuseQuery /データ取得ライブラリ(ルール 2)
ユーザー操作に応答するイベントハンドラー(ルール 3)
マウント時に一度だけ外部と同期するuseMountEffect(ルール 4)
prop の変更時に状態をリセットする親の key prop(ルール 5)

このスキルを使う場面

  • 新しい React コンポーネントを作成するとき
  • 既存の useEffect 呼び出しをリファクタリングするとき
  • useEffect を追加する PR をレビューするとき
  • エージェントが「念のため」useEffect を追加したとき

ワークフロー

1. useEffect の用途を特定する

エフェクトが何をしているか(状態の導出、データ取得、イベントへの応答、外部システムとの同期、状態のリセット)を確認します。

2. 適切な置き換えパターンを適用する

以下の 5 つのルールを使って、適切な置き換え方法を選びます。

3. 検証する
npm run lint -- --filter=<package>
npm run typecheck -- --filter=<package>
npm run test -- --filter=<package>

最終手段:useMountEffect

マウント時に外部システムと同期する必要があるまれなケースでは、次を使用します。

実装では、意図を明確にするため、空の依存配列を指定して useEffect をラップします。

export function useMountEffect(effect: () => void | (() => void)) {
  /* eslint-disable no-restricted-syntax */
  useEffect(effect, []);
}

置き換えパターン

ルール 1:状態を同期するのではなく、導出する

他の状態から状態を設定するエフェクトのほとんどは不要であり、余分な再レンダリングを発生させます。

// BAD: Two render cycles - first stale, then filtered
function ProductList() {
  const [products, setProducts] = useState([]);
  const [filteredProducts, setFilteredProducts] = useState([]);

  useEffect(() => {
    setFilteredProducts(products.filter((p) => p.inStock));
  }, [products]);
}

// GOOD: Compute inline in one render
function ProductList() {
  const [products, setProducts] = useState([]);
  const filteredProducts = products.filter((p) => p.inStock);
}

要注意の兆候: useEffect(() => setX(deriveFromY(y)), [y]) を書こうとしている、または他の状態や props を反映するだけの状態を持っている場合です。

ルール 2:データ取得ライブラリを使用する

エフェクトベースのデータ取得は、競合状態やキャッシュロジックの重複を招きます。

// BAD: Race condition risk
function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);

  useEffect(() => {
    fetchProduct(productId).then(setProduct);
  }, [productId]);
}

// GOOD: Query library handles cancellation/caching/staleness
function ProductPage({ productId }) {
  const { data: product } = useQuery(['product', productId], () =>
    fetchProduct(productId)
  );
}

要注意の兆候: エフェクト内で fetch(...) を実行してから setState(...) を呼び出している、またはキャッシュ、再試行、キャンセル、古いデータの処理を独自に実装している場合です。

ルール 3:エフェクトではなくイベントハンドラーを使う

ユーザーがボタンをクリックするなら、処理はハンドラー内で行います。

// BAD: Effect as an action relay
function LikeButton() {
  const [liked, setLiked] = useState(false);

  useEffect(() => {
    if (liked) {
      postLike();
      setLiked(false);
    }
  }, [liked]);

  return <button onClick={() => setLiked(true)}>Like</button>;
}

// GOOD: Direct event-driven action
function LikeButton() {
  return <button onClick={() => postLike()}>Like</button>;
}

要注意の兆候: エフェクトに実際の処理をさせるために状態をフラグとして使っている、または「フラグを設定 → エフェクトが実行 → フラグをリセット」という仕組みを作っている場合です。

ルール 4:一度だけの外部同期には useMountEffect を使う

適切な用途:DOM 連携(フォーカス、スクロール)、サードパーティ製ウィジェットのライフサイクル、ブラウザー API のサブスクリプション。

// BAD: Guard inside effect
function VideoPlayer({ isLoading }) {
  useEffect(() => {
    if (!isLoading) playVideo();
  }, [isLoading]);
}

// GOOD: Mount only when preconditions are met
function VideoPlayerWrapper({ isLoading }) {
  if (isLoading) return <LoadingScreen />;
  return <VideoPlayer />;
}

function VideoPlayer() {
  useMountEffect(() => playVideo());
}

依存関係が安定している場合(シングルトン、ref、変更されないコンテキスト値)は、useMountEffect を使用します。

// BAD: useEffect with dependency that never changes
useEffect(() => {
  connectionManager.on('connected', handleConnect);
  return () => connectionManager.off('connected', handleConnect);
}, [connectionManager]); // connectionManager is a singleton from context

// GOOD: useMountEffect for stable dependencies

useMountEffect(() => {
  connectionManager.on('connected', handleConnect);
  return () => connectionManager.off('connected', handleConnect);
});

要注意の兆候: 外部システムと同期しており、動作が自然に「マウント時にセットアップし、アンマウント時にクリーンアップする」形になる場合です。

ルール 5:依存関係を調整するのではなく、key でリセットする
// BAD: Effect attempts to emulate remount behavior
function VideoPlayer({ videoId }) {
  useEffect(() => {
    loadVideo(videoId);
  }, [videoId]);
}

// GOOD: key forces clean remount
function VideoPlayer({ videoId }) {
  useMountEffect(() => {
    loadVideo(videoId);
  });
}

function VideoPlayerWrapper({ videoId }) {
  return <VideoPlayer key={videoId} videoId={videoId} />;
}

要注意の兆候: ID/prop の変更時にローカル状態をリセットすることだけを目的とするエフェクトを書いている、またはエンティティごとにコンポーネントをまったく新しいインスタンスとして動作させたい場合です。

コンポーネント構造の規約

計算値はフックとローカル状態の後に記述し、useEffect は使いません。

export function FeatureComponent({ featureId }: ComponentProps) {
  // Hooks first
  const { data, isLoading } = useQueryFeature(featureId);

  // Local state
  const [isOpen, setIsOpen] = useState(false);

  // Computed values (NOT useEffect + setState)
  const displayName = user?.name ?? 'Unknown';

  // Event handlers
  const handleClick = () => { setIsOpen(true); };

  // Early returns
  if (isLoading) return <Loading />;

  // Render
  return <Flex direction="column" gap="lg">...</Flex>;
}

コメント