web-server-state-react-query について
Reactアプリでサーバーデータを管理する開発者向けのReact Query実践ガイドです。キャッシュ、型付きクエリオプション、エラー、ミューテーション、ページネーションを扱い、生成クライアントと手書きクエリの両方を対象にします。
主な機能
- 01型付きクエリオプションの生成
- 02クエリキーとキャッシュ設定
- 03クエリとミューテーションの一元的なエラー処理
- 04ミューテーションとキャッシュ無効化
- 05無限クエリとカーソルページング
活用シーン
- 01APIスキーマからクエリを生成
- 02データの鮮度とキャッシュ破棄の管理
- 03コンポーネント横断のエラー処理
- 04ページ分割されたデータ表示の構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-server-state-react-queryインストール後、Claude Code で/web-server-state-react-queryを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mderror-handling.mdinfinite-queries.mdmutations.mdsuspense.md詳細
React Query のパターン
クイックガイド: サーバーキャッシュを管理するのは React Query です。何を取得したか、どのくらいの間データが新鮮な状態に保たれるか、いつ削除されるかを管理します。各エントリーは
queryKeyで識別され、staleTimeとgcTimeはひとつではなく、別々のタイマーです。v5 ではuseQueryからonError/onSuccess/onSettledが削除されたため、エラーに伴う副作用はコンポーネントまたは共有のQueryCacheで処理します。
詳細なリソース:
- examples/core.md — 生成されたクライアントの出力、プロバイダーと認証の設定、
staleTimeとgcTimeの違い、クエリオプションの利用、フェッチのタイムアウト - examples/error-handling.md — コンポーネントの状態、グローバルハンドラー、リトライのバックオフ、エラーバウンダリー
- examples/mutations.md —
useMutation、無効化、UI またはキャッシュを介した楽観的更新 - examples/infinite-queries.md —
useInfiniteQuery、カーソルページネーション、maxPages - examples/suspense.md —
useSuspenseQuery、useSuspenseQueries、依存関係のある Suspense クエリ - reference.md — 設定とエラー戦略の参照、v4 → v5 の移行表
該当する方法
- 生成元となるスキーマが API にある場合 — スキーマから型、サービス関数、クエリオプションを生成し、
useQuery(generatedOptions())を呼び出します。キーと型がひとつのソースから生成されるため、スキーマの変更はコンパイルエラーとして検出されます。パターン 1。 - スキーマがない場合、または自分で管理していない場合 — クエリ関数を手動で記述し、解析時にレスポンスを検証したうえで、クエリキーを意図的に設計します。キーを自動生成するものがないためです。パターン 2 以降はすべてそのまま当てはまります。
<critical_requirements>
React Query のコードを書く前に
環境変数から API のベース URL を読み込みます。 ひとつのビルドで、すべての環境に対応できます。
スキーマが変更されたらクライアントを再生成し、生成された出力をコミットします。 再生成された型により、互換性を損なう API の変更を本番環境の実行時エラーではなく、レビュー時のコンパイルエラーとして検出できます。
</critical_requirements>
自動検出: useQuery、useMutation、useInfiniteQuery、useSuspenseQuery、QueryClient、QueryClientProvider、QueryCache、MutationCache、queryKey、queryOptions、staleTime、gcTime、invalidateQueries、placeholderData、initialPageParam、openapi-ts
対象範囲:
- サーバーデータのキャッシュ、無効化、再取得
- QueryClient のデフォルト設定、リトライポリシー、グローバルなエラー処理
- API スキーマからの型付きクエリオプションの生成
- 依存クエリ、条件付きクエリ、デバウンス付きクエリ
対象範囲外:
- サーバー由来ではないクライアント専用の状態 — このスキルはレスポンスをキャッシュするものであり、ユーザーが入力した状態については扱いません
- API スキーマの作成。このスキルが利用するのは作成済みのスキーマです
- グラフクエリ言語でアクセスする API。そのクライアントはレスポンス全体をキーでキャッシュするのではなく、正規化されたエンティティをキャッシュします
- 長時間接続するソケットストリーム。このキャッシュはリクエスト/レスポンス形式を対象とします
基本パターン
パターン 1:生成されたクエリオプション
ジェネレーターにスキーマを指定し、型、サービス関数、クエリオプションをまとめて出力させます。クエリキーも一緒に生成されるため、呼び出し箇所によってキーが食い違うことはありません。
// openapi-ts.config.ts
export default defineConfig({
input: "./openapi.yaml",
output: "src/api-client",
plugins: ["@hey-api/typescript", "@hey-api/sdk", "@tanstack/react-query"],
});
フェッチクライアントは @hey-api/openapi-ts v0.73 に同梱されています。オプションをカスタマイズする場合にのみ、クライアントプラグインを明示的に指定してください。
コード全文:examples/core.md
パターン 2:クライアントとキャッシュの設定
ベース URL と QueryClient のデフォルト設定は、プロバイダーで一度だけ設定します。クライアントを
useState 内で構築すれば、再レンダリング時にツリー配下のキャッシュが入れ替わることはありません。
const FIVE_MINUTES_MS = 5 * 60 * 1000;
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: { staleTime: FIVE_MINUTES_MS, refetchOnWindowFocus: false },
},
}),
);
client.setConfig({ baseUrl: process.env.API_BASE_URL ?? "" });
QueryClient の部分は、データの取得方法にかかわらず適用されます。client は生成されたクライアントであり、
その setConfig() は既存の設定を置き換えるのではなくマージするため、認証とカスタムの
fetch を別々の呼び出し箇所から設定できます。手書きのクエリ関数は、すでにベース URL を読み取っている場所から読み取ります。
完全なコード:examples/core.md — プロバイダーの設定、静的および動的な認証、タイムアウト時に中断する
fetch ラッパー
パターン 3:クエリオプションの利用と上書き
生成されたオプションを直接呼び出し、ある呼び出し箇所で異なるポリシーが必要な場合はスプレッドします。
const { data, isPending, error } = useQuery(getFeaturesOptions());
const TEN_MINUTES_MS = 10 * 60 * 1000;
const { data: slow } = useQuery({
...getFeaturesOptions(),
staleTime: TEN_MINUTES_MS,
enabled: someCondition,
});
ジェネレーターを使わない場合は、queryOptions ヘルパーで同じものを手書きし、型付けできます。
import { queryOptions } from "@tanstack/react-query";
const featuresOptions = () =>
queryOptions({ queryKey: ["features"], queryFn: getFeatures });
どちらの場合もキーと関数が 1 か所で宣言されるため、2 つの呼び出し箇所が 1 つのエンドポイントに対して 2 つのキャッシュエントリーを作成したり、
invalidateQueries が片方にしか届かなかったりするのを防げます。
オプションをスプレッドしてもキーは維持されるため、上書きしても別のエントリーは追加されず、同じエントリーを共有します。
完全なコード:examples/core.md
パターン 4:エラー処理
コンポーネントレベルの error はユーザーが見ているクエリを処理し、QueryCache と MutationCache
はそれ以外のすべてを 1 か所で処理します。
new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// A query that already held data failed a background refetch — the component shows stale
// data and no error, so the notification is the only signal.
if (query.state.data !== undefined) notify(error.message);
},
}),
mutationCache: new MutationCache({
onError: () => notify("Operation failed."),
}),
});
完全なコード:examples/error-handling.md
パターン 5:条件付きクエリとデバウンスされたクエリ
クエリキーにはデバウンスされた値を使い、enabled で実行を制御すれば、空の検索語がネットワークに送信されることはありません。
const DEBOUNCE_DELAY_MS = 500;
const debouncedTerm = useDebounce(searchTerm, DEBOUNCE_DELAY_MS);
const { data } = useQuery({
queryKey: ["search", debouncedTerm],
queryFn: () => searchApi(debouncedTerm),
enabled: debouncedTerm.length > 0,
});
生の検索語をキーにすると、キー入力のたびに 1 つずつキャッシュエントリーが作成されます。
<red_flags>
注意すべき点
実行時に問題が発生します:
useQueryでのonError/onSuccess/onSettled— v5 で削除されたため、副作用は実行されずに終わります。コンポーネントのerrorまたはQueryCache.onErrorを使ってください。- クエリ関数内での
client.setConfig()の呼び出し — 実行中のすべてのリクエストが共有する設定を変更するため、並行するクエリでベース URL の競合が発生します。 - 成功時に
clearTimeoutしないAbortControllerのタイムアウト — レスポンスが返った後もタイマーによってクロージャーが保持されます。 error分岐も上位のエラーバウンダリーもない状態でのクエリのレンダリング — fetch が拒否されると、サブツリー全体が停止します。
予想外の動作を引き起こします:
setConfig()は設定を置き換えるのではなくマージするため、一部だけを指定した呼び出しでは前の認証設定がそのまま残ります。- v5 ではサーバーサイドの再試行のデフォルトは
0です。v4 では 3 回再試行していました。 fetchのタイムアウトはstaleTimeやgcTimeとは無関係です。前者は 1 つのリクエストを制限し、後の 2 つはキャッシュエントリーを制限します。- ローカルモックに
retryを設定したままにすると、単に存在しないモックが、遅い失敗につながります。 - スキーマですでに型付けされているレスポンスに対してインターフェースを手書きすると、気付かないうちにずれが生じます。バックエンドが追加したフィールドは型に含まれず、そのフィールドを必要とするすべてのレンダリングにも含まれません。
- 生成されたオプションの隣に手書きの
useQueryラッパーを置くと、同じエンドポイントに対して 2 つのクエリキーと 2 つのキャッシュエントリーが作成されます。一方を無効化しても、もう一方は古いままです。
</red_flags>