mcpField
R
react-hooks

react-hooks

github.com/claude-dev-suite開発4636 回インストール3 日前に更新
リポジトリ

react-hooks について

React 開発者向けの Hooks リファレンススキルです。組み込みフック、カスタムパターン、利用規則、トラブルシューティングを扱います。

主な機能

  1. 01組み込みフックの用途とタイミング
  2. 02カスタムフックの一般的なパターン
  3. 03フックの呼び出し規則
  4. 04落とし穴と問題の診断

活用シーン

  1. 01コンポーネントの状態に合うフックの選択
  2. 02データ取得やデバウンス処理の実装
  3. 03依存関係や古いクロージャーの調査

MCP 対応

詳細な React Hooks 資料の取得先として、`mcp__documentation__fetch_docs` が案内されています。

インストール

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

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill react-hooks

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

同梱ファイルquick-ref/hooks-cheatsheet.md

詳細

React フック

詳細情報:React のすべてのフックとカスタムフックのパターンに関する包括的なドキュメントは、mcp__documentation__fetch_docs を technology: react topic: hooks で使用して取得してください。

組み込みフックのリファレンス

状態フック
フック目的使用する場面
useStateコンポーネント内の状態シンプルな状態値
useReducer複雑な状態ロジック複数のサブ値、複雑な更新
// useState - simple state
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

// useReducer - complex state
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset'; payload: number };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case 'increment': return state + 1;
    case 'decrement': return state - 1;
    case 'reset': return action.payload;
  }
}

const [count, dispatch] = useReducer(reducer, 0);
エフェクトフック
フック目的タイミング
useEffect副作用、サブスクリプション描画後
useLayoutEffectDOM の測定描画前
useInsertionEffectCSS-in-JS の挿入DOM の変更前
// useEffect - most common
useEffect(() => {
  const subscription = api.subscribe(id);
  return () => subscription.unsubscribe(); // Cleanup
}, [id]); // Dependency array

// useLayoutEffect - measure DOM before paint
useLayoutEffect(() => {
  const { height } = ref.current.getBoundingClientRect();
  setHeight(height);
}, []);
コンテキストフック
const ThemeContext = createContext<Theme>('light');

function Component() {
  const theme = useContext(ThemeContext); // Reads nearest Provider value
  return <div className={theme}>...</div>;
}
Ref フック
// DOM reference
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
  inputRef.current?.focus();
}, []);

// Mutable value (no re-render on change)
const renderCount = useRef(0);
renderCount.current++; // Doesn't cause re-render

// useImperativeHandle - customize ref exposed to parent
useImperativeHandle(ref, () => ({
  focus: () => inputRef.current?.focus(),
  scrollIntoView: () => inputRef.current?.scrollIntoView(),
}), []);
パフォーマンスフック
// useMemo - cache expensive calculations
const sortedItems = useMemo(() => {
  return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);

// useCallback - cache function references
const handleClick = useCallback((id: string) => {
  setSelected(id);
}, []); // Empty deps = stable reference

// useTransition - non-blocking state updates
const [isPending, startTransition] = useTransition();
startTransition(() => {
  setSearchResults(filterLargeList(query)); // Won't block UI
});

// useDeferredValue - defer updating expensive UI
const deferredQuery = useDeferredValue(query);
// UI shows stale value while new value computes
その他のフック
// useId - unique IDs for accessibility
const id = useId();
<label htmlFor={id}>Email</label>
<input id={id} type="email" />

// useSyncExternalStore - subscribe to external stores
const state = useSyncExternalStore(
  store.subscribe,
  store.getSnapshot,
  store.getServerSnapshot // Optional: for SSR
);

フックのルール

1. トップレベルでのみ呼び出す
// BAD - conditional hook
if (condition) {
  const [value, setValue] = useState(0); // ERROR
}

// BAD - hook in loop
for (const item of items) {
  useEffect(() => {}, [item]); // ERROR
}

// GOOD - conditional logic inside hook
const [value, setValue] = useState(0);
useEffect(() => {
  if (condition) {
    // Do something
  }
}, [condition]);
2. React の関数内でのみ呼び出す
// GOOD - in component
function Component() {
  const [state, setState] = useState(0);
}

// GOOD - in custom hook
function useCustomHook() {
  const [state, setState] = useState(0);
  return state;
}

// BAD - regular function
function helper() {
  const [state, setState] = useState(0); // ERROR
}

カスタムフックのパターン

基本パターン
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'dark');
読み込み/エラー状態を扱うフェッチフック
interface UseFetchResult<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

function useFetch<T>(url: string): UseFetchResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      setData(await response.json());
    } catch (e) {
      setError(e instanceof Error ? e : new Error(String(e)));
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
}
デバウンスフック
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);

useEffect(() => {
  if (debouncedQuery) {
    searchAPI(debouncedQuery);
  }
}, [debouncedQuery]);
トグルフック
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = useCallback(() => setValue(v => !v), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);

  return { value, toggle, setTrue, setFalse };
}

// Usage
const { value: isOpen, toggle, setFalse: close } = useToggle();
前回の値を取得するフック
function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// Usage - detect changes
const prevCount = usePrevious(count);
useEffect(() => {
  if (prevCount !== undefined && prevCount !== count) {
    console.log(`Count changed from ${prevCount} to ${count}`);
  }
}, [count, prevCount]);
メディアクエリフック
function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(() =>
    window.matchMedia(query).matches
  );

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);
    const handler = (e: MediaQueryListEvent) => setMatches(e.matches);

    mediaQuery.addEventListener('change', handler);
    return () => mediaQuery.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

// Usage
const isMobile = useMediaQuery('(max-width: 768px)');
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');

よくある落とし穴

1. 依存配列の不足
// BAD - missing userId dependency
useEffect(() => {
  fetchUser(userId);
}, []); // Lint error: userId missing

// GOOD
useEffect(() => {
  fetchUser(userId);
}, [userId]);
2. オブジェクト/配列の依存関係
// BAD - new object every render
useEffect(() => {
  doSomething(options);
}, [{ page: 1, limit: 10 }]); // Always triggers

// GOOD - use useMemo or primitive values
const options = useMemo(() => ({ page, limit }), [page, limit]);
useEffect(() => {
  doSomething(options);
}, [options]);
3. 古いクロージャー
// BAD - stale count value
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1); // Always uses initial count
  }, 1000);
  return () => clearInterval(interval);
}, []); // count not in deps but used

// GOOD - functional update
useEffect(() => {
  const interval = setInterval(() => {
    setCount(c => c + 1); // Uses current value
  }, 1000);
  return () => clearInterval(interval);
}, []);

このスキルを使用しない場面

  • React 19 の use() フック - 代わりに react-19 スキルを使用してください
  • Context API の実装 - Context のパターン全般については react-context スキルを使用してください
  • フォームの状態管理 - 代わりに react-forms または react-hook-form スキルを使用してください
  • パフォーマンス最適化 - 詳細な最適化戦略については react-performance スキルを使用してください

アンチパターン

アンチパターン問題解決策
条件付きでフックを呼び出すフックの順序が崩れる常にコンポーネントのトップレベルで呼び出す
依存関係の不足古いクロージャー配列にすべての依存関係を含める
状態があるのに依存配列が空コールバック内の状態が古くなる関数型更新を使うか、依存関係を追加する
オブジェクト/配列を依存関係にする常に実行されるuseMemo または個々のプリミティブを使う
派生状態に useEffect を使う余計な再レンダリングが発生するレンダリング中に計算する
エフェクトのクリーンアップをしないメモリーリーククリーンアップ関数を返す
カスタムフックの名前が 'use' で始まらないリンターによるチェックが行われないすべてのフックの名前を use* にする

すぐにできるトラブルシューティング

問題考えられる原因修正方法
フックの順序エラー条件付きのフック呼び出し条件をフックの内部に移動する
無限ループ依存関係の不足/誤り依存配列を確認する
古いクロージャー依存関係の不足依存配列に追加するか、関数型更新を使う
エフェクトが毎回のレンダリングで実行される依存配列の不足[] または適切な依存関係を追加する
クリーンアップが実行されない関数を返していないuseEffect からクリーンアップ関数を返す
useCallback が機能しない新しいインライン関数依存配列を確認する
useMemo がメモ化しない依存関係が変化している依存関係が安定しているか確認する

リファレンス

コメント