tanstack-query-v5 について
TanStack Query v5で非同期データを扱う、React・TypeScript開発者向けのスキルです。クエリキー、キャッシュ、ミューテーション、無限クエリを扱い、他の状態管理との使い分けも示します。
主な機能
- 01安定した階層型クエリキー
- 02非同期処理のキャッシュと再取得
- 03ミューテーションとクエリ無効化
- 04無限クエリとページネーション
- 05再利用可能なカスタムフック
活用シーン
- 01取得したサーバーデータのキャッシュ
- 02再利用可能なクエリロジックの作成
- 03ミューテーション後のデータ無効化
- 04無限スクロール型ページネーションの構築
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ blockmatic/ basilic-skills --skill tanstack-query-v5インストール後、Claude Code で/tanstack-query-v5を入力して読み込まれたか確認します。
詳細
スキル:tanstack-query
対象範囲
- 対象:非同期処理、データ取得、キャッシュ、ミューテーション、無限クエリ、楽観的更新における TanStack Query v5+
- 対象外:URL 状態管理(nuqs を使用)、グループ化された同期状態(ahooks.useSetState を使用)、localStorage の永続化(ahooks.useLocalStorageState を使用)
前提
- TanStack Query v5+
- フックをサポートする React 18+
- 型推論のための TypeScript v5+
- 共有クエリモジュールで手書きのクエリキータプルを使用(query-key-factory は任意)
- QueryClientProvider をアプリのルートで設定
原則
- キャッシュまたは状態管理のメリットがある非同期処理には、必ず TanStack Query を使用します
- フックの近く、または共有
queries/モジュールに配置した手書きのクエリキータプルを優先します isLoading、error、isErrorの状態を手動で管理しないでください(フックから提供されます)queryFnには、Promise を返す任意の関数を指定できます(HTTP 呼び出しに限りません)- データソースにかかわらず、すべての TanStack の機能は同じように動作します。キャッシュ、重複排除、バックグラウンドでの再取得、stale-while-revalidate などです
制約
必須
- 安定した階層構造のクエリキーを使用します(手書きのタプル、または任意で query-key-factory)
- 非同期処理には TanStack Query のフックを使用します(ローディング状態やエラー状態を手動で管理しないでください)
推奨
- キャッシュのメリットがある非同期処理(HTTP、localStorage の読み取り、ローカル計算、ファイル操作)には TanStack Query を使用します
- 再利用性を高めるため、クエリロジックをカスタムフックに切り出します
- 複数のクエリを一貫性のあるフックにまとめます
- 型安全なクエリには TypeScript のジェネリクスを使用します:
useQuery<ResponseType>(...) - QueryClient のデフォルト設定(retry、staleTime)はプロバイダーレベルで設定します
- プロジェクトですでに標準採用されている場合は、
@lukemorales/query-key-factoryを使用します
回避
- 共有プレフィックスを使わずに、無関係なクエリキーを無効化処理でハードコードすること
- ローディング状態やエラー状態を手動で管理すること(フックが提供する状態を使用してください)
- URL で共有する状態に使用すること(代わりに nuqs を使用してください)
- グループ化された同期状態に使用すること(代わりに ahooks.useSetState を使用してください)
- キャッシュの必要がない単発の Promise に使用すること(イベントハンドラー内で通常の async/await を使用してください)
連携
- URL 状態管理には nuqs-v2 と併用します(クエリは URL パラメーターに依存できます)
- 同期状態には ahooks-v3 と併用します(フォーム状態には useSetState、永続化には useLocalStorageState を使用します)
- OpenAPI で生成された API クライアントと連携します。React Query のフックは、それらのクライアントに対して手書きします
- 状態管理の判断ツリーの一部です(React のルールを参照)
パターン
手書きのクエリキー
フックと同じ場所に配置する安定したクエリキー:
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, id] as const,
list: (filters?: Filters) => [...userKeys.all, 'list', filters] as const,
}
// Usage
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
const { data, isLoading, error } = useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
任意の Query Key Factory
プロジェクトですでに @lukemorales/query-key-factory を使用している場合:
import { createQueryKeys } from '@lukemorales/query-key-factory'
export const users = createQueryKeys('users', {
detail: (id: string) => ({
queryKey: [id],
queryFn: () => fetchUser(id),
}),
})
汎用的な queryFn パターン
TanStack Query は、HTTP 呼び出しだけでなく、任意の非同期 queryFn を受け付けます:
// Server data fetching (traditional)
const { data } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
})
// Local computation (no network)
const { data } = useQuery({
queryKey: ['fibonacci', n],
queryFn: () => computeFibonacci(n),
})
// localStorage read
const { data: settings } = useQuery({
queryKey: ['user-settings'],
queryFn: () => Promise.resolve(
JSON.parse(localStorage.getItem('settings') || '{}')
),
})
// File operation
const { data } = useQuery({
queryKey: ['file-content', path],
queryFn: () => readFile(path),
})
使用する場合:自動キャッシュ、stale 状態の管理、または繰り返し実行のメリットがある非同期処理
使用しない場合:キャッシュの必要がない単発の Promise(イベントハンドラー内で通常の async/await を使用してください)
ミューテーションパターン
自動無効化を伴うミューテーション:
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (data: CreateUserInput) => {
const response = await createUser(data)
return response
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.all })
},
})
}
無限クエリパターン
無限スクロールを使ったページネーション:
import { useInfiniteQuery } from '@tanstack/react-query'
import { users } from '@/queries/users'
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: ({ pageParam = 0 }) => fetchUsers({ page: pageParam }),
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
initialPageParam: 0,
})
QueryClient の設定
プロバイダーレベルでデフォルト設定を行います:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
function Providers({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
refetchOnWindowFocus: false,
},
},
}))
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
手書きのフックパターン
生成された OpenAPI クライアントをカスタムフックでラップします:
import { useQuery } from '@tanstack/react-query'
import { apiClient } from '@/lib/api-client'
export function useHealthCheck() {
return useQuery({
queryKey: ['health'],
queryFn: () => apiClient.GET('/health').then(r => r.data),
})
}
カスタムフックパターン
クエリロジックを再利用可能なフックに切り出します:
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
export function useUser(userId: string) {
return useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
}
状態管理の意思決定ツリー
- URL で共有可能な状態 →
nuqsを使用します(フィルター、検索、タブ、ページネーション) - URL に含めないグループ化された状態 →
ahooks.useSetStateを使用します(フォームの状態、ゲームエンジン、一時的な UI) - 非同期処理 → TanStack Query を使用します(データ取得、ミューテーション、キャッシュ)
- localStorage への永続化 →
ahooks.useLocalStorageStateを使用します(環境設定、設定) - 単純で独立した状態 →
useStateを使用します(まれなケースです。他の選択肢を優先してください)
TanStack Query を使用するケース:サーバーデータの取得、ミューテーション、楽観的更新、バックグラウンドでの再取得、キャッシュ、状態管理の利点があるあらゆる非同期処理
TanStack Query を使用しないケース:URL で共有可能な状態(nuqs を使用)、グループ化された同期状態(ahooks.useSetState を使用)、キャッシュが不要な単発の Promise
参考資料
- TanStack Query ドキュメント - 公式ドキュメント
- Query Key Factory - クエリキーファクトリーライブラリ
- React のルール - 状態管理の意思決定ツリーとパターン
- ahooks - 同期状態用のユーティリティフック
- OpenAPI 統合 - 生成されたクライアントのパターン
コメント
Markdown 対応