From react-rules-plugin
Reactカスタムフック開発ルール。いつhookに切り出すか、API設計、effectの使いどころ、状態管理、テスト観点、アンチパターンを定義。カスタムフック実装時に参照。
How this skill is triggered — by the user, by Claude, or both
Slash command
/react-rules-plugin:custom-hookThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reactカスタムフックは「見た目を持たない再利用ロジック」を切り出すためのもの。
Reactカスタムフックは「見た目を持たない再利用ロジック」を切り出すためのもの。
コンポーネントを薄くするために使うが、抽象化のための抽象化はしない。
use で始めるopen close submit retry のような動詞にするhandleXxx は使わない。handle は component 内部向け| パターン | hookに向く理由 | 典型例 |
|---|---|---|
| 非同期データ取得 | loading / error / retry をまとめられる | useUserQuery |
| UI状態の再利用 | 開閉、選択、ページングの振る舞いを共有できる | useDisclosure |
| 外部システム同期 | event listener や subscription の cleanup を閉じ込められる | useMediaQuery |
| 複数stateの整合性維持 | state遷移を1つの公開APIにまとめられる | useAsyncTask |
useState ラッパーで、意味のある振る舞い追加がないisOpen isLoading hasNextPage のように意味が読める名前にするsetVisible(true) より open()useCallback / useMemo を増やさないuseEffect + setState で保持せず、render中に導出するdata error status submit のような状態と操作を返すonError / onSuccess を options で受けて実行してよいuseState / useEffect を使うhookはClient Component専用。RSC環境では呼び出し側に "use client" が必要window document localStorage などbrowser APIを使う場合は、SSRで実行されない前提を明示するuseState を優先するuseReducer を検討するloading data error isDirty のように、状態遷移をactionで整理した方が読める場合は useReducer が向くuseReducer が向く。例: isLoading && error && data のような曖昧状態を避けたいときdispatch は状態遷移の記述に使うtype UseDisclosureResult = {
isOpen: boolean;
open: () => void;
close: () => void;
toggle: () => void;
};
export const useDisclosure = (initialOpen = false): UseDisclosureResult => {
const [isOpen, setIsOpen] = useState(initialOpen);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const toggle = () => setIsOpen((prev) => !prev);
return { isOpen, open, close, toggle };
};
useEffect は外部システムとの同期にだけ使うuseEffect(() => { ...; return cleanup; }, []) を使ってよい[] は「初回だけ実行したいから」ではなく、「現在のprops/stateに追従する必要がない外部同期」に限るreact-hooks/exhaustive-deps の無効化は許容できる。その場合は「なぜ追従不要か」をコメントで残すuseEffect を使わないeslint-disable はしないuseEffectEvent を検討するuseEffectEvent が使えない場合だけ useRef で最新値を保持する。ref 同期は最後の手段として扱うuseRef は再レンダーでは保持されるが再マウントではリセットされる。timer や非同期処理と組み合わせる場合は cleanup を確実に書くdata isLoading error を検討するrefetch を返すuseActionState / useFormStatus で足りないか確認するuseOptimistic で置き換えられないか確認するuseEffect で自動取得する。ユーザー操作起点の取得まで mount時実行に寄せないsearch() submit() loadNext() のような公開関数から実行するAbortController、request id、実行中フラグなどで競合とstale responseを防ぐAbortController や有効フラグで終了処理を統一するisLoading と error の複数booleanが増えたら、status("idle" | "loading" | "success" | "error")で表現できないか検討するretry など)で意図を固定するtype UseUserSearchResult = {
users: User[];
isLoading: boolean;
error: Error | null;
search: (keyword: string) => Promise<void>;
};
export const useUserSearch = (): UseUserSearchResult => {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const latestRequestIdRef = useRef(0);
const search = useCallback(async (keyword: string) => {
const requestId = latestRequestIdRef.current + 1;
latestRequestIdRef.current = requestId;
setIsLoading(true);
setError(null);
try {
const nextUsers = await searchUsers(keyword);
if (latestRequestIdRef.current !== requestId) {
return;
}
setUsers(nextUsers);
} catch (nextError) {
if (latestRequestIdRef.current !== requestId) {
return;
}
setError(nextError as Error);
} finally {
if (latestRequestIdRef.current === requestId) {
setIsLoading(false);
}
}
}, []);
return { users, isLoading, error, search };
};
import { renderHook, waitFor } from "@testing-library/react";
test("後から呼んだ search の結果を採用する", async () => {
const { result } = renderHook(() => useUserSearch());
void result.current.search("a");
void result.current.search("ab");
await waitFor(() =>
expect(result.current.users).toEqual([{ id: "2", name: "ab" }]),
);
});
renderHook では実装詳細ではなく公開APIを検証するunmount を使って副作用解除を確認するerror 型は unknown のまま流さず、利用側が扱える型に正規化するtype UseXxxResult = { ... } として名前付きで定義し、API差分レビューをしやすくするuseEffect(() => setDerived(...), [source]) でderived valueをstate化するeslint-disable-next-line react-hooks/exhaustive-deps を付けて依存配列問題を隠すfoo, setFoo, bar, setBar, baz, setBaz を並べた巨大hookを作るuseFetchXxx を量産し、画面側で複数hookの結果を useEffect でつなぎ込むuseState を使うtoast.error("...") のような UI 実装を直書きするuseState より useReducer が適切な状態遷移なのに、更新が散らばっていないかuseEffect オーケストレーションが増えていないかnpx claudepluginhub dio0550/d-market-react --plugin react-rules-pluginProvides React hooks reference with syntax, patterns for useState/useReducer state, useEffect side effects/cleanup, useRef/useContext, useMemo/useCallback optimization, React 19 hooks, custom hooks, and best practices.
Provides patterns and examples for React Hooks including useState, useEffect, useContext, useMemo, useCallback, and custom hooks for state management and side effects.
Guides writing React hooks with conventions for object returns, SSR safety, state design, effect patterns, cleanup, TypeScript, and performance.