jpskill.com
🛠️ 開発・MCP コミュニティ

tanstack-query

TanStack Query v5の移行時の注意点、パフォーマンス問題、SWRやfetchとの使い分けなど、公式ドキュメントにはない、より実践的な問題解決や判断を支援するSkill。

📜 元の英語説明(参考)

TanStack Query v5 expert guidance - migration gotchas (v4→v5 breaking changes), performance pitfalls (infinite refetch loops, staleness traps), and decision frameworks (when NOT to use queries, SWR vs React Query trade-offs). Use when: (1) debugging v4→v5 migration errors (gcTime, isPending, throwOnError), (2) infinite refetch loops, (3) SSR hydration mismatches, (4) choosing between React Query vs SWR vs fetch, (5) optimistic update patterns not working. NOT for basic setup (see official docs). Focuses on non-obvious decisions and patterns that cause production issues. Triggers: React Query, TanStack Query, v5 migration, refetch loop, stale data, SSR hydration, query invalidation, optimistic updates debugging.

🇯🇵 日本人クリエイター向け解説

一言でいうと

TanStack Query v5の移行時の注意点、パフォーマンス問題、SWRやfetchとの使い分けなど、公式ドキュメントにはない、より実践的な問題解決や判断を支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tanstack-query.zip https://jpskill.com/download/9185.zip && unzip -o tanstack-query.zip && rm tanstack-query.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9185.zip -OutFile "$d\tanstack-query.zip"; Expand-Archive "$d\tanstack-query.zip" -DestinationPath $d -Force; ri "$d\tanstack-query.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して tanstack-query.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → tanstack-query フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

TanStack Query v5 - エキスパートトラブルシューティング

前提: あなたは useQuery の基本を知っているものとします。ここでは、本番環境で何が問題になるかを説明します。


React Query を使用する前の戦略的評価

useQuery を追加する前に、次の質問を自問してください。

1. データソースの分析

  • このデータはどこから来ますか?
    • URL パラメータ/パス → フレームワークローダー (Next.js, Remix) であり、React Query ではありません
    • 計算/導出 → useMemo であり、React Query ではありません
    • フォーム入力 → React Hook Form であり、React Query ではありません
    • REST/GraphQL → React Query ✅

2. 更新頻度とキャッシュ戦略

  • このデータはどのくらいの頻度で変更されますか?
    • リアルタイム (>1/秒) → WebSocket + Zustand (React Query のオーバーヘッドが高すぎる)
    • 頻繁 (<1/分) → アグレッシブな staleTime (30秒-1分) を持つ React Query
    • 中程度 (5-30分) → React Query 標準 (staleTime: 5分)
    • まれ (>1時間) → 長い staleTime (30分以上) を持つ React Query

3. 古いデータのコスト

  • ユーザーが古いデータを見た場合、どうなりますか?
    • クリティカル (お金、認証トークン) → staleTime: 0 (常に最新)
    • 重要 (ユーザーコンテンツ、メッセージ) → staleTime: 1-5分
    • あると便利 (分析、レコメンデーション) → staleTime: 30分以上

重要な決定: React Query を使用しない場合

データフェッチが必要ですか?
│
├─ URL からのデータ (検索パラメータ、パス) → クエリを使用しないでください
│   └─ フレームワークローダー (Next.js, Remix) または URL 状態を使用してください
│      理由: クエリはキーでキャッシュしますが、URL はすでにキャッシュキーです
│
├─ 派生/計算されたデータ → クエリを使用しないでください
│   └─ useMemo または Zustand を使用してください
│      理由: サーバーがなく、古いデータがなく、リフェッチは不要です
│
├─ フォーム状態 → クエリを使用しないでください
│   └─ React Hook Form または制御された状態を使用してください
│      理由: フォームはローカル状態であり、サーバーキャッシュではありません
│
├─ WebSocket/リアルタイムデータ → クエリを使用する可能性があります
│   ├─ 高頻度更新 (> 1/秒) → クエリを使用しないでください (Zustand を使用)
│   └─ 低頻度 (<1/分) → 手動更新でクエリを使用してください
│      理由: クエリはリクエスト/レスポンス用に設計されており、ストリーミング用ではありません
│
└─ REST/GraphQL サーバー状態 → クエリを使用してください
    (これが React Query の目的です)

落とし穴: 開発者はすべてに React Query を使用します。これはサーバーキャッシュであり、状態マネージャーではありません。


破壊的変更: v4 → v5 移行の注意点

❌ #1: cacheTimegcTime に名前変更

問題: サイレントな失敗 - コードは実行されますが、キャッシュは期待どおりに機能しません

// 間違い - v4 の構文、v5 では無視されます
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  cacheTime: 10 * 60 * 1000,  // ❌ v5 では無視されます
})

// 正しい - v5 の構文
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  gcTime: 10 * 60 * 1000,  // ✅ ガベージコレクション時間
})

なぜ壊れるのか: TypeScript は any または緩い型を使用している場合、エラーになりません。キャッシュは機能しているように見えますが、すぐにガベージコレクションされます。

なぜデバッグが難しいのか: エラーメッセージはありません - アプリは完全に実行されます。キャッシュは最初は機能しているように見えます。本番環境で 5 分以上経過すると、データのリフェッチが頻繁すぎ、ネットワークタブが点灯していることに気付きます。DevTools はクエリの gcTime が 0ms であることを示していますが、10 分に設定しました。プロパティは警告や TypeScript エラーなしに無視されます。「cacheTime が機能しない」を検索すると、v5 の移行ノートではなく、v4 の結果が見つかります。v4 ドキュメントと v5 ドキュメントを比較してキャッシュを検査するのに 20〜30 分かかり、プロパティの名前が変更されたことに気付きます。

❌ #2: isLoading が削除され、isPending を使用

問題: ローディングスピナーが早すぎるタイミングで消える

// 間違い - v4 の構文
const { isLoading } = useQuery(...)
if (isLoading) return <Spinner />  // ❌ v5 では isLoading は undefined

// 正しい - v5 の構文
const { isPending } = useQuery(...)
if (isPending) return <Spinner />  // ✅ クエリの保留中に表示されます

なぜ違うのか:

  • isLoading (v4): 最初のフェッチでのみ true (キャッシュされたデータなし)
  • isPending (v5): 最初のフェッチ + リフェッチで true (より正確)

移行の落とし穴: キャッシュされたデータがあり、リフェッチする場合、isPendingtrue のままですが、isLoadingfalse でした。UI は v5 で古いデータ + スピナーを表示します。

なぜデバッグが難しいのか: isLoadingundefined ですが、JavaScript はエラーになりません - if (undefined) は falsy であるため、スピナーは表示されません。UI は最初のテストでは機能しているように見えます (データは正常にロードされます)。特定の極端なケースでのみ - ユーザーがキャッシュされたデータを使用して離れて戻ってくる場合 - バグが発生します: リフェッチ中にローディング状態がありません。ユーザーは「ページが壊れているように感じる」と報告しますが、一貫して再現できません。DevTools は isLoading: undefined を示していますが、大きな状態オブジェクトでは見逃しがちです。v5 でプロパティが完全に削除され、代わりに isPending が必要であることに気付くのに 15〜20 分かかります。

❌ #3: keepPreviousData が削除され、placeholderData を使用

問題: ページネーションが壊れる - ページ変更時にちらつく

// 間違い - v4 の構文
useQuery({
  queryKey: ['todos', page],
  queryFn: () => fetchTodos(page),
  keepPreviousData: true,  // ❌ v5 で削除されました
})

// 正しい - v5 の構文
useQuery({
  queryKey: ['todos', page],
  queryFn: () => fetchTodos(page),
  placeholderData: (previousData) => previousData,  // ✅ 関数形式
})

なぜ壊れるのか: keepPreviousData: true はブール値でした。placeholderData はデータまたは関数です。true を渡すと、TypeScript エラーが発生しますが、ランタイムは壊れます。

❌ #4: クエリ関数は非 void を返す必要があります

問題: データを返さないミューテーションが壊れる

// 間違い - void の戻り値は v5 で壊れます
queryFn: async () => {
  await api.deleteTodo(id)  // ❌ void を返します
}

// 正しい - 何かを返す
queryFn: async () => {
  await api.deleteTodo(id)
  return { success: true }  // ✅ データを返す
}

なぜ壊れるのか: v5 型システムは Promise<void> ではなく Promise<TData> を必要とします。any を使用している場合は、サイレントランタイムエラーが発生します。


パフォーマンスの落とし穴

❌ 無限リフェッチループ

問題: クエリが永遠にリフェッチし、ブラウザがフリーズする


// 間違い - 無限ループを作成します
useQuery({
  queryKey: ['user', user],  // ❌ キー内のオブジェクト
  queryFn: () => fetchUser(user.id),
})

// なぜループするのか:
// 1. クエリが実行され、データを取得します
// 2. コンポーネントが再レンダリングされます
// 3. 新しい `user` オブジェクトが作成されます (異なる参照)
// 4. キー

(原文はここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

TanStack Query v5 - Expert Troubleshooting

Assumption: You know useQuery basics. This covers what breaks in production.


Before Using React Query: Strategic Assessment

Ask yourself these questions BEFORE adding useQuery:

1. Data Source Analysis

  • Where does this data come from?
    • URL params/path → Framework loader (Next.js, Remix), not React Query
    • Computation/derivation → useMemo, not React Query
    • Form input → React Hook Form, not React Query
    • REST/GraphQL → React Query ✅

2. Update Frequency & Caching Strategy

  • How often does this data change?
    • Real-time (>1/sec) → WebSocket + Zustand (React Query overhead too high)
    • Frequent (<1/min) → React Query with aggressive staleTime (30s-1min)
    • Moderate (5-30min) → React Query standard (staleTime: 5min)
    • Infrequent (>1hr) → React Query with long staleTime (30min+)

3. Cost of Stale Data

  • What happens if user sees old data?
    • Critical (money, auth tokens) → staleTime: 0 (always fresh)
    • Important (user content, messages) → staleTime: 1-5min
    • Nice-to-have (analytics, recommendations) → staleTime: 30min+

Critical Decision: When NOT to Use React Query

Need data fetching?
│
├─ Data from URL (search params, path) → DON'T use queries
│   └─ Use framework loaders (Next.js, Remix) or URL state
│      WHY: Queries cache by key, URL is already your cache key
│
├─ Derived/computed data → DON'T use queries
│   └─ Use useMemo or Zustand
│      WHY: No server, no stale data, no refetch needed
│
├─ Form state → DON'T use queries
│   └─ Use React Hook Form or controlled state
│      WHY: Forms are local state, not server cache
│
├─ WebSocket/realtime data → MAYBE use queries
│   ├─ High-frequency updates (> 1/sec) → DON'T use queries (use Zustand)
│   └─ Low-frequency (<1/min) → Use queries with manual updates
│      WHY: Queries designed for request/response, not streaming
│
└─ REST/GraphQL server state → USE queries
    (This is what React Query is for)

The trap: Developers use React Query for everything. It's a server cache, not a state manager.


Breaking Changes: v4 → v5 Migration Gotchas

❌ #1: cacheTime Renamed to gcTime

Problem: Silent failure - code runs but cache doesn't work as expected

// WRONG - v4 syntax, silently ignored in v5
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  cacheTime: 10 * 60 * 1000,  // ❌ Ignored in v5
})

// CORRECT - v5 syntax
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  gcTime: 10 * 60 * 1000,  // ✅ Garbage collection time
})

Why it breaks: TypeScript won't error if using any or loose types. Cache appears to work but garbage collects immediately.

Why this is deceptively hard to debug: No error messages—app runs perfectly. Cache appears functional initially. Only after 5+ minutes in production do you notice data refetching too frequently and Network tab lighting up. DevTools shows query has 0ms gcTime but you SET 10 minutes. The property is silently ignored—no warnings, no TypeScript errors. Takes 20-30 minutes of cache inspection comparing v4 docs to v5 docs to realize the property was renamed. Searches for "cacheTime not working" find v4 results, not v5 migration notes.

❌ #2: isLoading Removed, Use isPending

Problem: Loading spinners disappear too early

// WRONG - v4 syntax
const { isLoading } = useQuery(...)
if (isLoading) return <Spinner />  // ❌ isLoading undefined in v5

// CORRECT - v5 syntax
const { isPending } = useQuery(...)
if (isPending) return <Spinner />  // ✅ Shows while query pending

Why different:

  • isLoading (v4): true only for first fetch (no cached data)
  • isPending (v5): true for first fetch + refetches (more accurate)

Migration trap: If you have cached data and refetch, isPending stays true but isLoading was false. UI shows stale data + spinner in v5.

Why this is deceptively hard to debug: isLoading is undefined but JavaScript doesn't error—if (undefined) is falsy, so spinner never shows. UI appears to work in initial testing (data loads fine). Only in specific edge case—user navigates away and back with cached data—does the bug appear: no loading state during refetch. Users report "page feels broken" but can't reproduce consistently. DevTools shows isLoading: undefined but that's easy to miss in large state object. Takes 15-20 minutes to realize v5 removed the property entirely and you need isPending instead.

❌ #3: keepPreviousData Removed, Use placeholderData

Problem: Pagination breaks - flickers on page change

// WRONG - v4 syntax
useQuery({
  queryKey: ['todos', page],
  queryFn: () => fetchTodos(page),
  keepPreviousData: true,  // ❌ Removed in v5
})

// CORRECT - v5 syntax
useQuery({
  queryKey: ['todos', page],
  queryFn: () => fetchTodos(page),
  placeholderData: (previousData) => previousData,  // ✅ Function form
})

Why it breaks: keepPreviousData: true was boolean. placeholderData is data OR function. If you pass true, TypeScript error but runtime breaks.

❌ #4: Query Functions Must Return Non-Void

Problem: Mutations that don't return data break

// WRONG - void return breaks v5
queryFn: async () => {
  await api.deleteTodo(id)  // ❌ Returns void
}

// CORRECT - return something
queryFn: async () => {
  await api.deleteTodo(id)
  return { success: true }  // ✅ Return data
}

Why it breaks: v5 type system requires Promise<TData>, not Promise<void>. Silent runtime error if using any.


Performance Pitfalls

❌ Infinite Refetch Loop

Problem: Query refetches forever, browser freezes

// WRONG - creates infinite loop
useQuery({
  queryKey: ['user', user],  // ❌ Object in key
  queryFn: () => fetchUser(user.id),
})

// WHY IT LOOPS:
// 1. Query runs, gets data
// 2. Component re-renders
// 3. New `user` object created (different reference)
// 4. Key changes → query refetches
// 5. Goto 1 (infinite)

// CORRECT - use primitive values
useQuery({
  queryKey: ['user', user.id],  // ✅ String is stable
  queryFn: () => fetchUser(user.id),
})

Detection: React DevTools shows component re-rendering every frame. Network tab shows identical requests hammering server.

Why this is deceptively hard to debug: Page loads fine initially—first query succeeds. Then browser tab becomes unresponsive. CPU spikes to 100%. Network tab shows 50+ identical requests per second. React DevTools Profiler is unusable (too many renders). The cause—object reference in queryKey—is invisible in the network requests (they all look identical). Error isn't obvious: no stack trace, no warning, just performance death. Takes 10-15 minutes to realize it's React Query, then another 10-15 to isolate which query. Only after adding console.log to every query do you see one logging hundreds of times. The fix (extract primitive from object) is obvious once found, but finding the culprit query in a codebase with 50+ queries is the hard part.

❌ Stale Data Trap

Problem: Data never updates despite changes on server

// WRONG - data stuck in cache
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: Infinity,  // ❌ Never marks stale
})

// User adds todo on another tab → never sees it

// CORRECT - reasonable staleTime
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 5 * 60 * 1000,  // ✅ 5 minutes
})

Trade-off:

  • staleTime: 0 → Refetch on every focus/mount (expensive)
  • staleTime: Infinity → Never refetch (stale data)
  • staleTime: 5min → Balance (refetch after 5min of inactivity)

Why this is deceptively hard to debug: Works perfectly in development—you refresh constantly, clearing cache. In production, users keep tabs open for hours. They report "data doesn't update" but you can't reproduce (your dev habits differ). When you check DevTools, query shows fresh data (because you just opened DevTools, triggering window focus refetch if you have default settings). The user's staleTime: Infinity is buried in a hook 3 files deep. No error, no warning. You check the API—returns fresh data. You check network—no requests being made (that's the clue, but easy to miss). Takes 20-30 minutes of user reproduction videos to notice they never see network requests after initial load. Only then do you search the codebase for staleTime settings.

❌ Over-Invalidation

Problem: Unrelated data refetches on every mutation

// WRONG - nukes entire cache
onSuccess: () => {
  queryClient.invalidateQueries()  // ❌ Refetches EVERYTHING
}

// User updates profile → todos, posts, comments all refetch

// CORRECT - targeted invalidation
onSuccess: () => {
  queryClient.invalidateQueries({ queryKey: ['user', userId] })  // ✅ Only user
}

Why it hurts: 100 queries in cache → 100 network requests on every mutation. Kills mobile users.


Decision Frameworks

When to Use Optimistic Updates vs Invalidation

Mutation completes...
│
├─ Simple list append/prepend → Optimistic (useMutationState)
│   └─ Add todo, add comment, add item
│      WHY: No complex logic, just show pending item
│
├─ Complex computed data → Invalidation
│   └─ Change affects aggregates, filters, sorts
│      WHY: Server computes, client doesn't duplicate logic
│
├─ Risk of conflicts → Invalidation
│   └─ Multiple users editing same data
│      WHY: Optimistic update may be wrong, let server resolve
│
└─ Must feel instant → Optimistic + rollback on error
    └─ Toggle like, toggle favorite
       WHY: User expects immediate feedback

React Query vs SWR

Choose React Query when:
✅ Need fine-grained cache control (gc, stale times)
✅ Complex invalidation patterns
✅ Optimistic updates with rollback
✅ Infinite queries (pagination)
✅ Already using TanStack ecosystem (Table, Router)

Choose SWR when:
✅ Simpler API (less configuration)
✅ Automatic revalidation on focus is main use case
✅ Smaller bundle size priority
✅ Using Next.js (first-party support)

Real-world: React Query wins for complex apps, SWR wins for simple dashboards.


SSR Hydration Patterns (Next.js App Router)

❌ Common Hydration Mismatch

// WRONG - server renders loading, client renders data
function Page() {
  const { data, isPending } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  if (isPending) return <div>Loading...</div>  // ❌ Mismatch
  return <div>{data.map(...)}</div>
}

// SERVER: Renders "Loading..."
// CLIENT: Has cached data → renders list
// RESULT: Hydration error

✅ Correct Pattern with Prefetch

// app/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'

export default async function Page() {
  const queryClient = new QueryClient()

  // Prefetch on server
  await queryClient.prefetchQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <TodoList />  {/* Client Component */}
    </HydrationBoundary>
  )
}

// components/TodoList.tsx (Client Component)
'use client'

export function TodoList() {
  const { data } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  // No isPending check - data guaranteed from server
  return <div>{data.map(...)}</div>
}

Why it works: Server prefetches, client hydrates with same data, no mismatch.


Debugging Commands

Find refetch loops

// Add to QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onSuccess: (data, query) => {
        console.count(`Refetch: ${query.queryKey}`)
        // If count > 10 in 1 second → infinite loop
      }
    }
  }
})

Visualize cache state

import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

// Add to app
<ReactQueryDevtools initialIsOpen={false} />
// Click query → see refetch count, staleness, gc time

Test staleTime behavior

// Force immediate stale (for testing)
queryClient.invalidateQueries({ queryKey: ['todos'] })

// Check if query is stale
const state = queryClient.getQueryState(['todos'])
console.log(state?.isInvalidated)  // true = will refetch on next mount

Error Recovery Procedures

When Hydration Mismatch Occurs (SSR)

Recovery steps:

  1. Verify data equality: Add console.log(JSON.stringify(data)) in server and client components

  2. Check prefetch completion: Inspect dehydrate(queryClient) output—should contain queries object

  3. Confirm queryKey match: Server and client must use EXACT same key (including array order)

  4. Fallback: If still mismatching, bypass prefetch and use initialData from server props:

    // Server passes data via props
    export default function Page() {
      const data = await fetchTodos()
      return <TodoList initialTodos={data} />
    }
    
    // Client receives and uses as initialData
    function TodoList({ initialTodos }) {
      const { data } = useQuery({
        queryKey: ['todos'],
        queryFn: fetchTodos,
        initialData: initialTodos,  // Hydrates without mismatch
      })
    }

When Infinite Refetch Loop Detected

Recovery steps:

  1. Add refetch logging: console.count(\Refetch: \${JSON.stringify(queryKey)}`)` in queryFn
  2. Identify the culprit: Check which count exceeds 10 in first 2 seconds
  3. Extract primitives from queryKey: Replace objects/arrays with IDs/strings
  4. Fallback: If key MUST contain object (rare), memoize it AND disable structuralSharing:
    const stableKey = useMemo(() => ['user', user], [user.id])
    useQuery({
      queryKey: stableKey,
      queryFn: () => fetchUser(user.id),
      structuralSharing: false,  // Prevents reference comparison issues
    })

When Stale Data Persists

Recovery steps:

  1. Check staleTime setting: Search codebase for staleTime: Infinity or very large values
  2. Force invalidation: queryClient.invalidateQueries({ queryKey: ['your-key'] })
  3. Verify refetch: Check Network tab for request after invalidation
  4. Fallback: If data still stale, cache may be corrupted → clear and refetch:
    queryClient.removeQueries({ queryKey: ['your-key'] })
    queryClient.refetchQueries({ queryKey: ['your-key'] })

    Or nuclear option: queryClient.clear() (clears entire cache)

When v4→v5 Migration Breaks Silently

Recovery steps:

  1. Enable strict TypeScript: Add strict: true to tsconfig.json to catch removed properties
  2. Search for v4 property names: grep -r "cacheTime\|isLoading\|keepPreviousData" src/
  3. Replace with v5 equivalents: Use find-replace for codebase-wide fixes
  4. Fallback: If TypeScript still doesn't catch it, add runtime warning in development:
    // In QueryClient setup
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          // @ts-ignore - intentionally check for v4 props
          ...(process.env.NODE_ENV === 'development' && {
            // Warn if someone passes v4 props
            onError: (err, query) => {
              if ('cacheTime' in query) console.warn('⚠️ cacheTime removed in v5, use gcTime')
              if ('isLoading' in query) console.warn('⚠️ isLoading removed in v5, use isPending')
            }
          })
        }
      }
    })

When to Load Full Reference

MANDATORY - READ ENTIRE FILE: references/v5-features.md when:

  • Using 3+ v5-specific features simultaneously (useMutationState, throwOnError, infinite queries)
  • Need complete API reference for 5+ advanced hook options (select, placeholderData, notifyOnChangeProps)
  • Implementing complex patterns (optimistic updates with rollback, parallel/dependent queries, suspense mode)
  • Building custom hooks wrapping React Query with 4+ configuration options

MANDATORY - READ ENTIRE FILE: references/migration-guide.md when:

  • Migrating codebase with 10+ query usages from v4 to v5
  • Need exhaustive breaking changes checklist (20+ items to verify)
  • Encountering 3+ different v4→v5 migration errors
  • Setting up automated migration with codemods for large codebase (100+ queries)

Do NOT load references for:

  • Single breaking change fix (use this core framework's Breaking Changes section)
  • Basic troubleshooting (infinite loops, stale data, hydration—covered in core)
  • Simple optimistic update (use Decision Frameworks section)

Resources