mcpField
T
tanstack-query

tanstack-query

github.com/marcioaltoe開発063 回インストール今日更新
リポジトリ

tanstack-query について

React と TypeScript を使う TanStack Query v5 ユーザー向けの参照スキルです。クエリとミューテーション、キャッシュ、移行、SSR、オフライン対応、バージョン固有の問題を解説します。

主な機能

  1. 01クエリとミューテーションのパターン
  2. 02キャッシュ設定と無効化
  3. 03楽観的更新とエラー処理
  4. 04SSR、ページネーション、オフラインのガイド
  5. 05v4 から v5 への移行情報

活用シーン

  1. 01React アプリでのデータ取得実装
  2. 02プロジェクトの v4 から v5 への更新
  3. 03クエリのキャッシュと再取得の設定
  4. 04ハイドレーションやミューテーションの問題調査

インストール

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

$npx skills add https://github.com/marcioaltoe/skills --skill tanstack-query

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
rules/
cache-configuration.md
cache-invalidation.md
err-error-handling.md
inf-infinite-queries.md
mut-basics.md
mut-optimistic-updates.md
offline-support.md
parallel-queries.md
perf-optimization.md
pf-prefetching.md
qk-query-keys.md
ssr-hydration.md

詳細

TanStack Query v5

バージョン: @tanstack/react-[email protected] 必要条件: React 18.0+、TypeScript 4.7+

v5 の新機能

  • useMutationState — props のバケツリレーを使わずに、コンポーネント間でミューテーションを追跡します
  • シンプルな楽観的更新 — 保留中のミューテーションの variables を使うため、キャッシュを操作する必要はありません
  • throwOnError — useErrorBoundary から名称が変更されました
  • networkMode — オフライン/PWA に対応します(online | always | offlineFirst)
  • combine に対応した useQueries — 並列クエリの結果を 1 つのオブジェクトにまとめます
  • infiniteQueryOptions — 無限クエリ向けの型安全なファクトリーです(queryOptions に対応)
  • maxPages — 無限クエリのキャッシュ内のページ数を制限します(双方向ページネーションが必要です)
  • ミューテーションのコールバックシグネチャの変更(v5.89+) — onError/onSuccess/onSettled が 4 つのパラメーターを受け取るようになりました(onMutateResult が追加されました)

クイックセットアップ

npm install @tanstack/react-query@latest @tanstack/react-query-devtools@latest
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 min
      gcTime: 1000 * 60 * 60,   // 1 hour
      refetchOnWindowFocus: false,
    },
  },
})

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}
統合 Devtools(TanStack ライブラリを複数使う場合に推奨)

Query と Router(またはほかの TanStack ライブラリ)を使う場合は、個別の Devtools コンポーネントではなく、統合された TanStackDevtools シェルを使用してください。

npm install -D @tanstack/react-devtools
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
import { TanStackDevtools } from '@tanstack/react-devtools'

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <TanStackDevtools
        config={{ position: 'bottom-right' }}
        plugins={[
          { name: 'TanStack Query', render: <ReactQueryDevtoolsPanel /> },
          // Add more plugins: Router, etc.
        ]}
      />
    </QueryClientProvider>
  )
}

TanStackDevtools 内に埋め込む場合は、*Panel バリアント(ReactQueryDevtoolsPanel、TanStackRouterDevtoolsPanel)を使用してください。

import { useQuery, useMutation, useQueryClient, queryOptions } from '@tanstack/react-query'

const todosQueryOptions = queryOptions({
  queryKey: ['todos'],
  queryFn: async () => {
    const res = await fetch('/api/todos')
    if (!res.ok) throw new Error('Failed to fetch')
    return res.json()
  },
})

function useTodos() {
  return useQuery(todosQueryOptions)
}

function useAddTodo() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: async (newTodo: { title: string }) => {
      const res = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newTodo),
      })
      if (!res.ok) throw new Error('Failed to add')
      return res.json()
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })
}

ルールのカテゴリ

優先度カテゴリルールファイル影響
CRITICALクエリキーrules/qk-query-keys.mdキャッシュの不具合やデータの不整合を防ぎます
CRITICALキャッシュrules/cache-configuration.mdパフォーマンスとデータの鮮度を最適化します
HIGH無効化rules/cache-invalidation.md古くなったデータが適切に更新されるようにします
HIGHミューテーションrules/mut-basics.md書き込み後のデータ整合性を保ちます
HIGH楽観的更新rules/mut-optimistic-updates.mdミューテーション中も UI の応答性を保ちます
HIGHエラー処理rules/err-error-handling.mdユーザー体験の低下を防ぎます
MEDIUMプリフェッチrules/pf-prefetching.md体感パフォーマンスを向上させます
MEDIUM無限クエリrules/inf-infinite-queries.mdページネーションの不具合を防ぎます
MEDIUMSSR/ハイドレーションrules/ssr-hydration.md適切なサーバーレンダリングを可能にします
MEDIUM並列クエリrules/parallel-queries.md動的な並列データ取得を可能にします
LOWパフォーマンスrules/perf-optimization.md不要な再レンダリングを減らします
LOWオフライン対応rules/offline-support.mdオフラインファーストのパターンを可能にします

重要なルール

必ず行うこと
  • すべてのフックでオブジェクト構文を使用する:useQuery({ queryKey, queryFn, ...options })
  • クエリキーには配列を使用する:['todos']、['todos', id]、['todos', { filter }]
  • queryFn でエラーをスローする:if (!res.ok) throw new Error('Failed')
  • 初回読み込みには isPending を使用する:if (isPending) return <Loading />
  • ミューテーション後に無効化する:onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
  • queryOptions ファクトリーを使用する:useQuery、useSuspenseQuery、prefetchQuery 間で再利用します
  • gcTime を使用する(cacheTime ではなく):v5 で名称が変更されました
絶対に行わないこと
  • v4 の配列/関数構文:useQuery(['todos'], fetchTodos) — v5 で削除されました
  • クエリのコールバック:クエリから onSuccess/onError/onSettled が削除されました(ミューテーションでは引き続き使用できます)。代わりに useEffect を使用してください
  • 「まだデータがない」状態の判定に isLoading を使うこと:v5 で意味が変わりました。isPending を使用してください
  • useSuspenseQuery で enabled を使うこと:使用できません。条件付きレンダリングを使用してください
  • keepPreviousData:削除されました。placeholderData: keepPreviousData を使用してください
  • パラメーターの変更に refetch() を使うこと:代わりにパラメーターを queryKey に含めてください。クエリは自動的に再取得されます

v4→v5 移行チートシート

v4v5備考
useQuery(['key'], fn, opts)useQuery({ queryKey, queryFn, ...opts })オブジェクト構文のみ
cacheTimegcTime名前を変更
isLoading (データなし)isPendingisLoading = isPending && isFetching
keepPreviousData: trueplaceholderData: keepPreviousDatakeepPreviousData ヘルパーをインポート
useErrorBoundarythrowOnError名前を変更
クエリの onSuccess/onError/onSettled削除副作用には useEffect を使用
pageParam = 0 のデフォルト値initialPageParam: 0無限クエリでは必須
status: 'loading'status: 'pending'名前を変更
onError(err, vars, ctx)onError(err, vars, onMutateResult, ctx)v5.89+ で4番目のパラメーターを追加

既知の問題(v5.90.x)

  • ストリーミング SSR のハイドレーション不一致 — void prefetchQuery と useSuspenseQuery を併用し、条件付きで isFetching に基づいてレンダリングすると、ハイドレーションエラーが発生します。回避策:prefetch を await するか、fetchStatus に基づくレンダリングを避けてください
  • プリフェッチ時の useQuery ハイドレーションエラー — useQuery とサーバー側のプリフェッチを併用すると、サーバーとクライアント間で isLoading が一致しない場合があります。代わりに useSuspenseQuery を使用してください
  • エラー状態のクエリでは refetchOnMount が無視される — エラーは常に古い状態として扱われます。refetchOnMount: false に加えて retryOnMount: false を使用してください
  • useMutationState の型 — あいまいな照合により、mutation.state.variables は unknown 型になります。select コールバック内で明示的にキャストしてください
  • invalidateQueries はアクティブなクエリのみを再取得する — 非アクティブなクエリも含めるには refetchType: 'all' を使用してください
  • 読み取り専用のクエリキーが v5.90.8 で動作しない — v5.90.9+ で修正済み

主なパターン

// Dependent queries (B waits for A)
const { data: user } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
const { data: posts } = useQuery({
  queryKey: ['posts', user?.id],
  queryFn: () => fetchPosts(user!.id),
  enabled: !!user,
})

// Parallel queries
const results = useQueries({
  queries: ids.map(id => ({ queryKey: ['item', id], queryFn: () => fetchItem(id) })),
  combine: (results) => ({ data: results.map(r => r.data), pending: results.some(r => r.isPending) }),
})

// Prefetch on hover
const handleHover = () => queryClient.prefetchQuery({ queryKey: ['item', id], queryFn: () => fetchItem(id) })

// Infinite scroll
useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam }) => fetchPosts(pageParam),
  initialPageParam: 0,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
})

// Query cancellation
queryFn: async ({ signal }) => {
  const res = await fetch(`/api/search?q=${query}`, { signal })
  return res.json()
}

// Data transformation
useQuery({ queryKey: ['todos'], queryFn: fetchTodos, select: (data) => data.filter(t => t.completed) })

コメント