[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FGZ9HDJM5EXB4WZDFXNSV1":3,"skill-same:01M6FGZ9HDJM5EXB4WZDFXNSV1":189},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":21,"linkOnly":22,"markdownContent":23,"markdownBody":24,"frontMatter":25,"refName":26,"commitSha":27,"skillDirectory":28,"forksCount":29,"repositoryHtmlUrl":30,"repositoryHomepageUrl":31,"ownerHtmlUrl":32,"ownerBlogUrl":6,"ownerDescription":33,"updatedAt":34,"install":35,"resources":48},"01M6FGZ9HDJM5EXB4WZDFXNSV1","tanstack-query",null,"ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002FSKILL.md",[],[18,19,20],"tanstack","tanstack query","query","2026-11-10 10:12:30",false,"---\nname: tanstack-query\ndescription: TanStack Query v5 (React Query) server state management. Use for data fetching, caching, mutations, or encountering v4 migration, stale data, invalidation errors.\n\n  Keywords: TanStack Query, React Query, useQuery, useMutation, useInfiniteQuery, useSuspenseQuery, QueryClient, QueryClientProvider, data fetching, server state, caching, staleTime, gcTime, query invalidation, prefetching, optimistic updates, mutations, query keys, query functions, error boundaries, suspense, React Query DevTools, v5 migration, v4 to v5, request waterfalls, background refetching, cacheTime renamed, loading status renamed, pending status, initialPageParam required, keepPreviousData removed, placeholderData, query callbacks removed, onSuccess removed, onError removed, object syntax required\nlicense: MIT\n---\n\n# TanStack Query (React Query) v5\n\n**Status**: Production Ready ✅\n**Last Updated**: 2025-12-09\n**Dependencies**: React 18.0+ (18.3+ recommended), TypeScript 4.9+ (5.x preferred)\n**Latest Versions**: @tanstack\u002Freact-query@5.90.12, @tanstack\u002Freact-query-devtools@5.91.1, @tanstack\u002Feslint-plugin-query@5.91.2\n\n---\n\n## Quick Start (5 Minutes)\n\n### 1. Install Dependencies\n\n```bash\n# choose your package manager\npnpm add @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n# or\nnpm install @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n# or\nbun add @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n```\n\n**Why this matters:**\n- TanStack Query v5 requires React 18+ (uses useSyncExternalStore)\n- DevTools are essential for debugging queries and mutations\n- v5 has breaking changes from v4 - use latest for all fixes\n\n### 2. Set Up QueryClient Provider\n\n```tsx\n\u002F\u002F src\u002Fmain.tsx or src\u002Findex.tsx\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom\u002Fclient'\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query'\nimport { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools'\nimport App from '.\u002FApp'\n\n\u002F\u002F Create a client\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n      gcTime: 1000 * 60 * 60, \u002F\u002F 1 hour (formerly cacheTime)\n      retry: 1,\n      refetchOnWindowFocus: false,\n    },\n  },\n})\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003CStrictMode>\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CApp \u002F>\n      \u003CReactQueryDevtools initialIsOpen={false} \u002F>\n    \u003C\u002FQueryClientProvider>\n  \u003C\u002FStrictMode>\n)\n```\n\n**CRITICAL:**\n- Wrap entire app with `QueryClientProvider`\n- Configure `staleTime` to avoid excessive refetches (default is 0)\n- Use `gcTime` (not `cacheTime` - renamed in v5)\n- DevTools should be inside provider\n\n**Know the defaults (v5):**\n- `staleTime: 0` → data is immediately stale, so refetches on mount\u002Ffocus unless you raise it\n- `gcTime: 5 * 60 * 1000` → inactive data is garbage-collected after 5 minutes\n- `retry: 3` in browsers, `retry: 0` on the server\n- `refetchOnWindowFocus: true` and `refetchOnReconnect: true`\n- `networkMode: 'online'` (requests pause while offline). Switch to `'always'` for SSR\u002Fprefetch where you don't want cancellation. citeturn1search0turn1search1\n\n### 3. Create First Query\n\n```tsx\n\u002F\u002F src\u002Fhooks\u002FuseTodos.ts\nimport { useQuery } from '@tanstack\u002Freact-query'\n\ntype Todo = {\n  id: number\n  title: string\n  completed: boolean\n}\n\nasync function fetchTodos(): Promise\u003CTodo[]> {\n  const response = await fetch('\u002Fapi\u002Ftodos')\n  if (!response.ok) {\n    throw new Error('Failed to fetch todos')\n  }\n  return response.json()\n}\n\nexport function useTodos() {\n  return useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n  })\n}\n\n\u002F\u002F Usage in component:\nfunction TodoList() {\n  const { data, isPending, isError, error } = useTodos()\n\n  if (isPending) return \u003Cdiv>Loading...\u003C\u002Fdiv>\n  if (isError) return \u003Cdiv>Error: {error.message}\u003C\u002Fdiv>\n\n  return (\n    \u003Cul>\n      {data.map(todo => (\n        \u003Cli key={todo.id}>{todo.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**CRITICAL:**\n- v5 requires object syntax: `useQuery({ queryKey, queryFn })`\n- Use `isPending` (not `isLoading` - that now means \"pending AND fetching\")\n- Always throw errors in queryFn for proper error handling\n- QueryKey should be array for consistent cache keys\n\n### 4. Create First Mutation\n\n```tsx\n\u002F\u002F src\u002Fhooks\u002FuseAddTodo.ts\nimport { useMutation, useQueryClient } from '@tanstack\u002Freact-query'\n\ntype NewTodo = {\n  title: string\n}\n\nasync function addTodo(newTodo: NewTodo) {\n  const response = await fetch('\u002Fapi\u002Ftodos', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify(newTodo),\n  })\n  if (!response.ok) throw new Error('Failed to add todo')\n  return response.json()\n}\n\nexport function useAddTodo() {\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: addTodo,\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch todos\n      queryClient.invalidateQueries({ queryKey: ['todos'] })\n    },\n  })\n}\n\n\u002F\u002F Usage in component:\nfunction AddTodoForm() {\n  const { mutate, isPending } = useAddTodo()\n\n  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n    e.preventDefault()\n    const formData = new FormData(e.currentTarget)\n    mutate({ title: formData.get('title') as string })\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={isPending}>\n        {isPending ? 'Adding...' : 'Add Todo'}\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n```\n\n**Why this works:**\n- Mutations use callbacks (`onSuccess`, `onError`, `onSettled`) - queries don't\n- `invalidateQueries` triggers background refetch\n- Mutations don't cache by default (correct behavior)\n\n---\n\n## The 7-Step Setup Process\n\n### Step 1: Install Dependencies\n\n```bash\n# Core library (required)\npnpm add @tanstack\u002Freact-query\n\n# DevTools (highly recommended for development)\npnpm add -D @tanstack\u002Freact-query-devtools\n\n# Optional: ESLint plugin for best practices\npnpm add -D @tanstack\u002Feslint-plugin-query\n```\n\n**Package roles:**\n- `@tanstack\u002Freact-query` - Core React hooks and QueryClient\n- `@tanstack\u002Freact-query-devtools` - Visual debugger (dev only, tree-shakeable)\n- `@tanstack\u002Feslint-plugin-query` - Catches common mistakes\n\n**Version requirements:**\n- React 18.0 or higher (uses `useSyncExternalStore`)\n- TypeScript 5.2+ for best type inference (optional but recommended)\n\n### Step 2: Configure QueryClient\n\n```tsx\n\u002F\u002F src\u002Flib\u002Fquery-client.ts\nimport { QueryClient } from '@tanstack\u002Freact-query'\n\nexport const queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      \u002F\u002F How long data is considered fresh (won't refetch during this time)\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n\n      \u002F\u002F How long inactive data stays in cache before garbage collection\n      gcTime: 1000 * 60 * 60, \u002F\u002F 1 hour (v5: renamed from cacheTime)\n\n      \u002F\u002F Retry failed requests (0 on server, 3 on client by default)\n      retry: (failureCount, error) => {\n        if (error instanceof Response && error.status === 404) return false\n        return failureCount \u003C 3\n      },\n\n      \u002F\u002F Refetch on window focus (can be annoying during dev)\n      refetchOnWindowFocus: false,\n\n      \u002F\u002F Refetch on network reconnect\n      refetchOnReconnect: true,\n\n      \u002F\u002F Refetch on component mount if data is stale\n      refetchOnMount: true,\n    },\n    mutations: {\n      \u002F\u002F Retry mutations on failure (usually don't want this)\n      retry: 0,\n    },\n  },\n})\n```\n\n**Key configuration decisions:**\n\n**staleTime vs gcTime:**\n- `staleTime`: How long until data is considered \"stale\" and might refetch\n  - `0` (default): Data is immediately stale, refetches on mount\u002Ffocus\n  - `1000 * 60 * 5`: Data fresh for 5 min, no refetch during this time\n  - `Infinity`: Data never stale, manual invalidation only\n- `gcTime`: How long unused data stays in cache\n  - `1000 * 60 * 5` (default): 5 minutes\n  - `Infinity`: Never garbage collect (memory leak risk)\n\n**When to refetch:**\n- `refetchOnWindowFocus: true` - Good for frequently changing data (stock prices)\n- `refetchOnWindowFocus: false` - Good for stable data or during development\n- `refetchOnMount: true` - Ensures fresh data when component mounts\n- `refetchOnReconnect: true` - Refetch after network reconnect\n\n### Step 3: Wrap App with Provider\n\n```tsx\n\u002F\u002F src\u002Fmain.tsx\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom\u002Fclient'\nimport { QueryClientProvider } from '@tanstack\u002Freact-query'\nimport { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools'\nimport { queryClient } from '.\u002Flib\u002Fquery-client'\nimport App from '.\u002FApp'\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003CStrictMode>\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CApp \u002F>\n      \u003CReactQueryDevtools\n        initialIsOpen={false}\n        buttonPosition=\"bottom-right\"\n      \u002F>\n    \u003C\u002FQueryClientProvider>\n  \u003C\u002FStrictMode>\n)\n```\n\n**Provider placement:**\n- Must wrap all components that use TanStack Query hooks\n- DevTools must be inside provider\n- Only one QueryClient instance for entire app\n\n**DevTools configuration:**\n- `initialIsOpen={false}` - Collapsed by default\n- `buttonPosition=\"bottom-right\"` - Where to show toggle button\n- Automatically removed in production builds (tree-shaken)\n\n### Advanced Setup (Steps 4-7)\n\n**For detailed patterns**: Load `references\u002Fadvanced-setup.md` when implementing custom query hooks, mutations with optimistic updates, DevTools configuration, or error boundaries.\n\n**Quick summaries:**\n\n**Step 4: Custom Query Hooks** - Use `queryOptions` factory for reusable patterns. Create custom hooks that encapsulate API calls.\n\n**Step 5: Mutations** - Use `useMutation` with `onSuccess` to invalidate queries. For instant UI feedback, implement optimistic updates with `onMutate`\u002F`onError`\u002F`onSettled` pattern.\n\n**Step 6: DevTools** - Already included in Step 3. Advanced options for customization available in reference.\n\n**Step 7: Error Boundaries** - Use `QueryErrorResetBoundary` with React Error Boundary. Configure `throwOnError` option for global vs local error handling.\n\n---\n\n## Critical Rules\n\n### Always Do\n\n✅ **Use object syntax for all hooks**\n```tsx\n\u002F\u002F v5 ONLY supports this:\nuseQuery({ queryKey, queryFn, ...options })\nuseMutation({ mutationFn, ...options })\n```\n\n✅ **Use array query keys**\n```tsx\nqueryKey: ['todos']              \u002F\u002F List\nqueryKey: ['todos', id]          \u002F\u002F Detail\nqueryKey: ['todos', { filter }]  \u002F\u002F Filtered\n```\n\n✅ **Configure staleTime appropriately**\n```tsx\nstaleTime: 1000 * 60 * 5 \u002F\u002F 5 min - prevents excessive refetches\n```\n\n✅ **Use isPending for initial loading state**\n```tsx\nif (isPending) return \u003CLoading \u002F>\n\u002F\u002F isPending = no data yet AND fetching\n```\n\n✅ **Throw errors in queryFn**\n```tsx\nif (!response.ok) throw new Error('Failed')\n```\n\n✅ **Invalidate queries after mutations**\n```tsx\nonSuccess: () => {\n  queryClient.invalidateQueries({ queryKey: ['todos'] })\n}\n```\n\n✅ **Use queryOptions factory for reusable patterns**\n```tsx\nconst opts = queryOptions({ queryKey, queryFn })\nuseQuery(opts)\nuseSuspenseQuery(opts)\nprefetchQuery(opts)\n```\n\n✅ **Use gcTime (not cacheTime)**\n```tsx\ngcTime: 1000 * 60 * 60 \u002F\u002F 1 hour\n```\n\n✅ **Know your status flags**\n```tsx\nisPending      \u002F\u002F no data yet, fetch in flight\nisFetching     \u002F\u002F any fetch in flight (including refetch)\nisRefetching   \u002F\u002F refetch specifically (data already cached)\nisLoadingError \u002F\u002F initial load failed\nisPaused       \u002F\u002F networkMode paused (e.g., offline)\nisFetchingNextPage \u002F\u002F useInfiniteQuery loading more\n```\n\n### Never Do\n\n❌ **Never use v4 array\u002Ffunction syntax**\n```tsx\n\u002F\u002F v4 (removed in v5):\nuseQuery(['todos'], fetchTodos, options) \u002F\u002F ❌\n\n\u002F\u002F v5 (correct):\nuseQuery({ queryKey: ['todos'], queryFn: fetchTodos }) \u002F\u002F ✅\n```\n\n❌ **Never use query callbacks (onSuccess, onError, onSettled in queries)**\n```tsx\n\u002F\u002F v5 removed these from queries:\nuseQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n  onSuccess: (data) => {}, \u002F\u002F ❌ Removed in v5\n})\n\n\u002F\u002F Use useEffect instead:\nconst { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })\nuseEffect(() => {\n  if (data) {\n    \u002F\u002F Do something\n  }\n}, [data])\n\n\u002F\u002F Or use mutation callbacks (still supported):\nuseMutation({\n  mutationFn: addTodo,\n  onSuccess: () => {}, \u002F\u002F ✅ Still works for mutations\n})\n```\n\n❌ **Never use deprecated options**\n```tsx\n\u002F\u002F Deprecated in v5:\ncacheTime: 1000 \u002F\u002F ❌ Use gcTime instead\nisLoading: true \u002F\u002F ❌ Meaning changed, use isPending\nkeepPreviousData: true \u002F\u002F ❌ Use placeholderData instead\nonSuccess: () => {} \u002F\u002F ❌ Removed from queries\nuseErrorBoundary: true \u002F\u002F ❌ Use throwOnError instead\n```\n\n❌ **Never assume isLoading means \"no data yet\"**\n```tsx\n\u002F\u002F v5 changed this:\nisLoading = isPending && isFetching \u002F\u002F ❌ Now means \"pending AND fetching\"\nisPending = no data yet \u002F\u002F ✅ Use this for initial load\n```\n\n❌ **Never forget initialPageParam for infinite queries**\n```tsx\n\u002F\u002F v5 requires this:\nuseInfiniteQuery({\n  queryKey: ['projects'],\n  queryFn: ({ pageParam }) => fetchProjects(pageParam),\n  initialPageParam: 0, \u002F\u002F ✅ Required in v5\n  getNextPageParam: (lastPage) => lastPage.nextCursor,\n})\n```\n\n❌ **Never use enabled with useSuspenseQuery**\n```tsx\n\u002F\u002F Not allowed:\nuseSuspenseQuery({\n  queryKey: ['todo', id],\n  queryFn: () => fetchTodo(id),\n  enabled: !!id, \u002F\u002F ❌ Not available with suspense\n})\n\n\u002F\u002F Use conditional rendering instead:\n{id && \u003CTodoComponent id={id} \u002F>}\n```\n\n---\n\n## Error Prevention\n\nThis skill prevents **8+ documented v5 migration issues**. The most critical errors include:\n- Object syntax required (v4 function overloads removed)\n- Query callbacks removed (onSuccess\u002FonError\u002FonSettled)\n- `isPending` vs `isLoading` status changes\n- `cacheTime` renamed to `gcTime`\n- `initialPageParam` required for infinite queries\n- `keepPreviousData` replaced with `placeholderData`\n\n**For complete error catalog with before\u002Fafter examples**: Load `references\u002Ftop-errors.md` when encountering errors or debugging v5 migration issues.\n\n---\n\n## Project Configuration\n\n**Essential configuration files**: package.json, tsconfig.json, .eslintrc.cjs\n\n**Key requirements**:\n- React 18.3.1+ (uses useSyncExternalStore)\n- TypeScript strict mode recommended\n- ESLint plugin catches v4→v5 migration errors\n\n**For complete configuration templates**: Load `references\u002Fconfiguration-files.md` when setting up new projects or troubleshooting build\u002Ftype errors.\n\n---\n\n## Common Patterns\n\n### Pattern 1: Dependent Queries\n\n```tsx\n\u002F\u002F Fetch user, then fetch user's posts\nfunction UserPosts({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  const { data: posts } = useQuery({\n    queryKey: ['users', userId, 'posts'],\n    queryFn: () => fetchUserPosts(userId),\n    enabled: !!user, \u002F\u002F Only fetch posts after user is loaded\n  })\n\n  if (!user) return \u003Cdiv>Loading user...\u003C\u002Fdiv>\n  if (!posts) return \u003Cdiv>Loading posts...\u003C\u002Fdiv>\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>{user.name}\u003C\u002Fh1>\n      \u003Cul>\n        {posts.map(post => (\n          \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**When to use**: Query B depends on data from Query A\n\n### Pattern 2: Parallel Queries with useQueries\n\n```tsx\n\u002F\u002F Fetch multiple todos in parallel\nfunction TodoDetails({ ids }: { ids: number[] }) {\n  const results = useQueries({\n    queries: ids.map(id => ({\n      queryKey: ['todos', id],\n      queryFn: () => fetchTodo(id),\n    })),\n  })\n\n  const isLoading = results.some(result => result.isPending)\n  const isError = results.some(result => result.isError)\n\n  if (isLoading) return \u003Cdiv>Loading...\u003C\u002Fdiv>\n  if (isError) return \u003Cdiv>Error loading todos\u003C\u002Fdiv>\n\n  return (\n    \u003Cul>\n      {results.map((result, i) => (\n        \u003Cli key={ids[i]}>{result.data?.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**When to use**: Fetch multiple independent queries in parallel\n\n### Pattern 3: Prefetching\n\n```tsx\nimport { useQueryClient } from '@tanstack\u002Freact-query'\nimport { todosQueryOptions } from '.\u002Fhooks\u002FuseTodos'\n\nfunction TodoListWithPrefetch() {\n  const queryClient = useQueryClient()\n  const { data: todos } = useTodos()\n\n  const prefetchTodo = (id: number) => {\n    queryClient.prefetchQuery({\n      queryKey: ['todos', id],\n      queryFn: () => fetchTodo(id),\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n    })\n  }\n\n  return (\n    \u003Cul>\n      {todos?.map(todo => (\n        \u003Cli\n          key={todo.id}\n          onMouseEnter={() => prefetchTodo(todo.id)}\n        >\n          \u003CLink to={`\u002Ftodos\u002F${todo.id}`}>{todo.title}\u003C\u002FLink>\n        \u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**When to use**: Preload data before user navigates (on hover, on mount)\n\n### Pattern 4: Infinite Scroll\n\n```tsx\nimport { useInfiniteQuery } from '@tanstack\u002Freact-query'\nimport { useEffect, useRef } from 'react'\n\ntype Page = {\n  data: Todo[]\n  nextCursor: number | null\n}\n\nasync function fetchTodosPage({ pageParam }: { pageParam: number }): Promise\u003CPage> {\n  const response = await fetch(`\u002Fapi\u002Ftodos?cursor=${pageParam}&limit=20`)\n  return response.json()\n}\n\nfunction InfiniteTodoList() {\n  const {\n    data,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = useInfiniteQuery({\n    queryKey: ['todos', 'infinite'],\n    queryFn: fetchTodosPage,\n    initialPageParam: 0,\n    getNextPageParam: (lastPage) => lastPage.nextCursor,\n  })\n\n  const loadMoreRef = useRef\u003CHTMLDivElement>(null)\n\n  useEffect(() => {\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries[0].isIntersecting && hasNextPage) {\n          fetchNextPage()\n        }\n      },\n      { threshold: 0.1 }\n    )\n\n    if (loadMoreRef.current) {\n      observer.observe(loadMoreRef.current)\n    }\n\n    return () => observer.disconnect()\n  }, [fetchNextPage, hasNextPage])\n\n  return (\n    \u003Cdiv>\n      {data?.pages.map((page, i) => (\n        \u003Cdiv key={i}>\n          {page.data.map(todo => (\n            \u003Cdiv key={todo.id}>{todo.title}\u003C\u002Fdiv>\n          ))}\n        \u003C\u002Fdiv>\n      ))}\n\n      \u003Cdiv ref={loadMoreRef}>\n        {isFetchingNextPage && \u003Cdiv>Loading more...\u003C\u002Fdiv>}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**When to use**: Paginated lists with infinite scroll\n\n### Pattern 5: Query Cancellation\n\n```tsx\nfunction SearchTodos() {\n  const [search, setSearch] = useState('')\n\n  const { data } = useQuery({\n    queryKey: ['todos', 'search', search],\n    queryFn: async ({ signal }) => {\n      const response = await fetch(`\u002Fapi\u002Ftodos?q=${search}`, { signal })\n      return response.json()\n    },\n    enabled: search.length > 2, \u002F\u002F Only search if 3+ characters\n  })\n\n  return (\n    \u003Cdiv>\n      \u003Cinput\n        value={search}\n        onChange={e => setSearch(e.target.value)}\n        placeholder=\"Search todos...\"\n      \u002F>\n      {data && (\n        \u003Cul>\n          {data.map(todo => (\n            \u003Cli key={todo.id}>{todo.title}\u003C\u002Fli>\n          ))}\n        \u003C\u002Ful>\n      )}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**How it works:**\n- When queryKey changes, previous query is automatically cancelled\n- Pass `signal` to fetch for proper cleanup\n- Browser aborts pending fetch requests\n\n### Pattern 6: Background Fetch Indicators\n\n```tsx\nconst { data, isFetching, isRefetching } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n  staleTime: 1000 * 60 * 5,\n})\n\nreturn (\n  \u003Cdiv>\n    {isFetching && \u003CSpinner label={isRefetching ? 'Refreshing…' : 'Loading…'} \u002F>}\n    \u003CTodoList data={data} \u002F>\n  \u003C\u002Fdiv>\n)\n```\n\n**Why:** `isFetching` stays true during background refetches so you can show a subtle \"Refreshing\" badge without losing cached data.\n\n---\n\n## Using Bundled Resources\n\n### Templates (templates\u002F)\n\nComplete, copy-ready code examples:\n\n- `package.json` - Dependencies with exact versions\n- `query-client-config.ts` - QueryClient setup with best practices\n- `provider-setup.tsx` - App wrapper with QueryClientProvider\n- `use-query-basic.tsx` - Basic useQuery hook pattern\n- `use-mutation-basic.tsx` - Basic useMutation hook\n- `use-mutation-optimistic.tsx` - Optimistic update pattern\n- `use-infinite-query.tsx` - Infinite scroll pattern\n- `custom-hooks-pattern.tsx` - Reusable query hooks with queryOptions\n- `error-boundary.tsx` - Error boundary with query reset\n- `devtools-setup.tsx` - DevTools configuration\n\n**Example Usage:**\n```bash\n# Copy query client config\ncp ~\u002F.claude\u002Fskills\u002Ftanstack-query\u002Ftemplates\u002Fquery-client-config.ts src\u002Flib\u002F\n\n# Copy provider setup\ncp ~\u002F.claude\u002Fskills\u002Ftanstack-query\u002Ftemplates\u002Fprovider-setup.tsx src\u002Fmain.tsx\n\n# Or run the bootstrap helper (installs deps + copies core files):\n.\u002Fscripts\u002Fexample-script.sh . pnpm\n```\n\n### References (references\u002F)\n\nDeep-dive documentation loaded when needed:\n\n- `advanced-setup.md` - Custom hooks, mutations, optimistic updates, DevTools, error boundaries\n- `configuration-files.md` - Complete package.json, tsconfig.json, .eslintrc.cjs templates\n- `v4-to-v5-migration.md` - Complete v4 → v5 migration guide\n- `best-practices.md` - Request waterfalls, caching strategies, performance\n- `common-patterns.md` - Reusable queries, optimistic updates, infinite scroll\n- `official-guides-map.md` - When to open each official doc and what it covers\n- `typescript-patterns.md` - Type safety, generics, type inference\n- `testing.md` - Testing with MSW, React Testing Library\n- `top-errors.md` - All 8+ errors with solutions\n\n### Examples (examples\u002F)\n\n- `examples\u002FREADME.md` - Index of top 10 scenarios with official links\n- `basic.tsx` - Minimal list query\n- `basic-graphql-request.tsx` - GraphQL client + select\n- `optimistic-update.tsx` - onMutate snapshot\u002Frollback\n- `pagination.tsx` - paginated list with placeholderData\n- `infinite-scroll.tsx` - useInfiniteQuery + IntersectionObserver\n- `prefetching.tsx` - prefetch on hover before navigation\n- `suspense.tsx` - useSuspenseQuery + boundary\n- `default-query-function.ts` - global fetcher using queryKey\n- `nextjs-app-router.tsx` - App Router prefetch + hydrate (`networkMode: 'always'`)\n- `react-native.tsx` - offline-first with AsyncStorage persister\n\n**When Claude should load these:**\n- `advanced-setup.md` - When implementing custom query hooks, mutations, or error boundaries\n- `configuration-files.md` - When setting up new projects or troubleshooting build\u002Ftype errors\n- `v4-to-v5-migration.md` - When migrating existing React Query v4 project\n- `best-practices.md` - When optimizing performance or avoiding waterfalls\n- `common-patterns.md` - When implementing specific features (infinite scroll, etc.)\n- `typescript-patterns.md` - When dealing with TypeScript errors or type inference\n- `testing.md` - When writing tests for components using TanStack Query\n- `top-errors.md` - When encountering errors not covered in main SKILL.md\n\n---\n\n## Advanced Topics\n\n### Data Transformations with select\n\n```tsx\n\u002F\u002F Only subscribe to specific slice of data\nfunction TodoCount() {\n  const { data: count } = useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n    select: (data) => data.length, \u002F\u002F Only re-render when count changes\n  })\n\n  return \u003Cdiv>Total todos: {count}\u003C\u002Fdiv>\n}\n\n\u002F\u002F Transform data shape\nfunction CompletedTodoTitles() {\n  const { data: titles } = useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n    select: (data) =>\n      data\n        .filter(todo => todo.completed)\n        .map(todo => todo.title),\n  })\n\n  return (\n    \u003Cul>\n      {titles?.map((title, i) => (\n        \u003Cli key={i}>{title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**Benefits:**\n- Component only re-renders when selected data changes\n- Reduces memory usage (less data stored in component state)\n- Keeps query cache unchanged (other components get full data)\n\n### Request Waterfalls (Anti-Pattern)\n\n```tsx\n\u002F\u002F ❌ BAD: Sequential waterfalls\nfunction BadUserProfile({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  const { data: posts } = useQuery({\n    queryKey: ['posts', user?.id],\n    queryFn: () => fetchPosts(user!.id),\n    enabled: !!user,\n  })\n\n  const { data: comments } = useQuery({\n    queryKey: ['comments', posts?.[0]?.id],\n    queryFn: () => fetchComments(posts![0].id),\n    enabled: !!posts && posts.length > 0,\n  })\n\n  \u002F\u002F Each query waits for previous one = slow!\n}\n\n\u002F\u002F ✅ GOOD: Fetch in parallel when possible\nfunction GoodUserProfile({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  \u002F\u002F Fetch posts AND comments in parallel\n  const { data: posts } = useQuery({\n    queryKey: ['posts', userId],\n    queryFn: () => fetchPosts(userId), \u002F\u002F Don't wait for user\n  })\n\n  const { data: comments } = useQuery({\n    queryKey: ['comments', userId],\n    queryFn: () => fetchUserComments(userId), \u002F\u002F Don't wait for posts\n  })\n\n  \u002F\u002F All 3 queries run in parallel = fast!\n}\n```\n\n### Server State vs Client State\n\n```tsx\n\u002F\u002F ❌ Don't use TanStack Query for client-only state\nconst { data: isModalOpen, setData: setIsModalOpen } = useMutation(...)\n\n\u002F\u002F ✅ Use useState for client state\nconst [isModalOpen, setIsModalOpen] = useState(false)\n\n\u002F\u002F ✅ Use TanStack Query for server state\nconst { data: todos } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n})\n```\n\n**Rule of thumb:**\n- Server state: Use TanStack Query (data from API)\n- Client state: Use useState\u002FuseReducer (local UI state)\n- Global client state: Use Zustand\u002FContext (theme, auth token)\n\n---\n\n## Platform & Integration Notes\n\n- **React Native**: Works the same as web. Use `@tanstack\u002Fquery-async-storage-persister` to persist cache to AsyncStorage; avoid window-focus refetch logic. DevTools panel not available natively—use Flipper or expose logs.\n- **GraphQL**: Pair with `graphql-request` or urql's bare client. Treat operations as plain async functions; co-locate fragments and use `select` to map edges\u002Fnodes to flat shapes.\n- **SSR \u002F Next.js \u002F TanStack Start**: Use `dehydrate`\u002F`HydrationBoundary` on the server and `QueryClientProvider` on the client. Set `networkMode: 'always'` for server prefetches so requests are never paused.\n- **Suspense**: Prefer `useSuspenseQuery` for routes already using Suspense. Do not combine with `enabled`; gate rendering instead.\n- **Testing**: Use `@testing-library\u002Freact` + `@tanstack\u002Freact-query\u002Ftesting` helpers and mock network with MSW. Reset QueryClient between tests to avoid cache bleed.\n\n---\n\n## Dependencies\n\n**Required**:\n- `@tanstack\u002Freact-query@5.90.12` - Core library\n- `react@18.0.0+` - Uses useSyncExternalStore hook\n- `react-dom@18.0.0+` - React DOM renderer\n\n**Recommended**:\n- `@tanstack\u002Freact-query-devtools@5.91.1` - Visual debugger (dev only)\n- `@tanstack\u002Feslint-plugin-query@5.91.2` - ESLint rules for best practices\n- `typescript@5.2.0+` - For type safety and inference\n\n**Optional**:\n- `@tanstack\u002Fquery-sync-storage-persister` - Persist cache to localStorage\n- `@tanstack\u002Fquery-async-storage-persister` - Persist to AsyncStorage (React Native)\n\n---\n\n## Official Documentation\n\n- **TanStack Query Docs**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\n- **React Integration**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Foverview\n- **v5 Migration Guide**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Fguides\u002Fmigrating-to-v5\n- **API Reference**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Freference\u002FuseQuery\n- **Context7 Library ID**: `\u002Fwebsites\u002Ftanstack_query`\n- **GitHub Repository**: https:\u002F\u002Fgithub.com\u002FTanStack\u002Fquery\n- **Discord Community**: https:\u002F\u002Ftlinz.com\u002Fdiscord\n\n---\n\n## Package Versions (Verified 2025-12-09)\n\n```json\n{\n  \"dependencies\": {\n    \"@tanstack\u002Freact-query\": \"^5.90.12\"\n  },\n  \"devDependencies\": {\n    \"@tanstack\u002Freact-query-devtools\": \"^5.91.1\",\n    \"@tanstack\u002Feslint-plugin-query\": \"^5.91.2\"\n  }\n}\n```\n\n**Verification:**\n- `npm view @tanstack\u002Freact-query version` → 5.90.12\n- `npm view @tanstack\u002Freact-query-devtools version` → 5.91.1\n- `npm view @tanstack\u002Feslint-plugin-query version` → 5.91.2\n- Last checked: 2025-12-09\n\n---\n\n## Production Example\n\nThis skill is based on production patterns used in:\n- **Build Time**: ~6 hours research + development\n- **Errors Prevented**: 8 (all documented v5 migration issues)\n- **Token Efficiency**: ~65% savings vs manual setup\n- **Validation**: ✅ All patterns tested with TypeScript strict mode\n\n---\n\n## Troubleshooting\n\n### Problem: \"useQuery is not a function\" or type errors\n**Solution**: Ensure you're using v5 object syntax:\n```tsx\n\u002F\u002F ✅ Correct:\nuseQuery({ queryKey: ['todos'], queryFn: fetchTodos })\n\n\u002F\u002F ❌ Wrong (v4 syntax):\nuseQuery(['todos'], fetchTodos)\n```\n\n### Problem: Callbacks (onSuccess, onError) not working on queries\n**Solution**: Removed in v5. Use `useEffect` or move to mutations:\n```tsx\n\u002F\u002F ✅ For queries:\nconst { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })\nuseEffect(() => {\n  if (data) {\n    \u002F\u002F Handle success\n  }\n}, [data])\n\n\u002F\u002F ✅ For mutations (still work):\nuseMutation({\n  mutationFn: addTodo,\n  onSuccess: () => { \u002F* ... *\u002F },\n})\n```\n\n### Problem: isLoading always false even during initial load\n**Solution**: Use `isPending` instead:\n```tsx\nconst { isPending, isLoading, isFetching } = useQuery(...)\n\u002F\u002F isPending = no data yet\n\u002F\u002F isLoading = isPending && isFetching\n\u002F\u002F isFetching = any fetch in progress\n```\n\n### Problem: cacheTime option not recognized\n**Solution**: Renamed to `gcTime` in v5:\n```tsx\ngcTime: 1000 * 60 * 60 \u002F\u002F 1 hour\n```\n\n### Problem: useSuspenseQuery with enabled option gives type error\n**Solution**: `enabled` not available with suspense. Use conditional rendering:\n```tsx\n{id && \u003CTodoComponent id={id} \u002F>}\n```\n\n### Problem: Data not refetching after mutation\n**Solution**: Invalidate queries in `onSuccess`:\n```tsx\nonSuccess: () => {\n  queryClient.invalidateQueries({ queryKey: ['todos'] })\n}\n```\n\n### Problem: Infinite query requires initialPageParam\n**Solution**: Always provide `initialPageParam` in v5:\n```tsx\nuseInfiniteQuery({\n  queryKey: ['projects'],\n  queryFn: ({ pageParam }) => fetchProjects(pageParam),\n  initialPageParam: 0, \u002F\u002F Required\n  getNextPageParam: (lastPage) => lastPage.nextCursor,\n})\n```\n\n### Problem: keepPreviousData not working\n**Solution**: Replaced with `placeholderData`:\n```tsx\nimport { keepPreviousData } from '@tanstack\u002Freact-query'\n\nuseQuery({\n  queryKey: ['todos', page],\n  queryFn: () => fetchTodos(page),\n  placeholderData: keepPreviousData,\n})\n```\n\n---\n\n## Complete Setup Checklist\n\nUse this checklist to verify your setup:\n\n- [ ] Installed @tanstack\u002Freact-query@5.90.12+\n- [ ] Installed @tanstack\u002Freact-query-devtools (dev dependency)\n- [ ] Created QueryClient with configured defaults\n- [ ] Wrapped app with QueryClientProvider\n- [ ] Added ReactQueryDevtools component\n- [ ] Created first query using object syntax\n- [ ] Tested isPending and error states\n- [ ] Created first mutation with onSuccess handler\n- [ ] Set up query invalidation after mutations\n- [ ] Configured staleTime and gcTime appropriately\n- [ ] Using array queryKey consistently\n- [ ] Throwing errors in queryFn\n- [ ] No v4 syntax (function overloads)\n- [ ] No query callbacks (onSuccess, onError on queries)\n- [ ] Using isPending (not isLoading) for initial load\n- [ ] DevTools working in development\n- [ ] TypeScript types working correctly\n- [ ] Production build succeeds\n\n---\n\n**Questions? Issues?**\n\n1. Check `references\u002Ftop-errors.md` for complete error solutions\n2. Verify all steps in the setup process\n3. Check official docs: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\n4. Ensure using v5 syntax (object syntax, gcTime, isPending)\n5. Join Discord: https:\u002F\u002Ftlinz.com\u002Fdiscord\n","# TanStack Query (React Query) v5\n\n**Status**: Production Ready ✅\n**Last Updated**: 2025-12-09\n**Dependencies**: React 18.0+ (18.3+ recommended), TypeScript 4.9+ (5.x preferred)\n**Latest Versions**: @tanstack\u002Freact-query@5.90.12, @tanstack\u002Freact-query-devtools@5.91.1, @tanstack\u002Feslint-plugin-query@5.91.2\n\n---\n\n## Quick Start (5 Minutes)\n\n### 1. Install Dependencies\n\n```bash\n# choose your package manager\npnpm add @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n# or\nnpm install @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n# or\nbun add @tanstack\u002Freact-query@latest @tanstack\u002Freact-query-devtools@latest\n```\n\n**Why this matters:**\n- TanStack Query v5 requires React 18+ (uses useSyncExternalStore)\n- DevTools are essential for debugging queries and mutations\n- v5 has breaking changes from v4 - use latest for all fixes\n\n### 2. Set Up QueryClient Provider\n\n```tsx\n\u002F\u002F src\u002Fmain.tsx or src\u002Findex.tsx\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom\u002Fclient'\nimport { QueryClient, QueryClientProvider } from '@tanstack\u002Freact-query'\nimport { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools'\nimport App from '.\u002FApp'\n\n\u002F\u002F Create a client\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n      gcTime: 1000 * 60 * 60, \u002F\u002F 1 hour (formerly cacheTime)\n      retry: 1,\n      refetchOnWindowFocus: false,\n    },\n  },\n})\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003CStrictMode>\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CApp \u002F>\n      \u003CReactQueryDevtools initialIsOpen={false} \u002F>\n    \u003C\u002FQueryClientProvider>\n  \u003C\u002FStrictMode>\n)\n```\n\n**CRITICAL:**\n- Wrap entire app with `QueryClientProvider`\n- Configure `staleTime` to avoid excessive refetches (default is 0)\n- Use `gcTime` (not `cacheTime` - renamed in v5)\n- DevTools should be inside provider\n\n**Know the defaults (v5):**\n- `staleTime: 0` → data is immediately stale, so refetches on mount\u002Ffocus unless you raise it\n- `gcTime: 5 * 60 * 1000` → inactive data is garbage-collected after 5 minutes\n- `retry: 3` in browsers, `retry: 0` on the server\n- `refetchOnWindowFocus: true` and `refetchOnReconnect: true`\n- `networkMode: 'online'` (requests pause while offline). Switch to `'always'` for SSR\u002Fprefetch where you don't want cancellation. citeturn1search0turn1search1\n\n### 3. Create First Query\n\n```tsx\n\u002F\u002F src\u002Fhooks\u002FuseTodos.ts\nimport { useQuery } from '@tanstack\u002Freact-query'\n\ntype Todo = {\n  id: number\n  title: string\n  completed: boolean\n}\n\nasync function fetchTodos(): Promise\u003CTodo[]> {\n  const response = await fetch('\u002Fapi\u002Ftodos')\n  if (!response.ok) {\n    throw new Error('Failed to fetch todos')\n  }\n  return response.json()\n}\n\nexport function useTodos() {\n  return useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n  })\n}\n\n\u002F\u002F Usage in component:\nfunction TodoList() {\n  const { data, isPending, isError, error } = useTodos()\n\n  if (isPending) return \u003Cdiv>Loading...\u003C\u002Fdiv>\n  if (isError) return \u003Cdiv>Error: {error.message}\u003C\u002Fdiv>\n\n  return (\n    \u003Cul>\n      {data.map(todo => (\n        \u003Cli key={todo.id}>{todo.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**CRITICAL:**\n- v5 requires object syntax: `useQuery({ queryKey, queryFn })`\n- Use `isPending` (not `isLoading` - that now means \"pending AND fetching\")\n- Always throw errors in queryFn for proper error handling\n- QueryKey should be array for consistent cache keys\n\n### 4. Create First Mutation\n\n```tsx\n\u002F\u002F src\u002Fhooks\u002FuseAddTodo.ts\nimport { useMutation, useQueryClient } from '@tanstack\u002Freact-query'\n\ntype NewTodo = {\n  title: string\n}\n\nasync function addTodo(newTodo: NewTodo) {\n  const response = await fetch('\u002Fapi\u002Ftodos', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify(newTodo),\n  })\n  if (!response.ok) throw new Error('Failed to add todo')\n  return response.json()\n}\n\nexport function useAddTodo() {\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: addTodo,\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch todos\n      queryClient.invalidateQueries({ queryKey: ['todos'] })\n    },\n  })\n}\n\n\u002F\u002F Usage in component:\nfunction AddTodoForm() {\n  const { mutate, isPending } = useAddTodo()\n\n  const handleSubmit = (e: React.FormEvent\u003CHTMLFormElement>) => {\n    e.preventDefault()\n    const formData = new FormData(e.currentTarget)\n    mutate({ title: formData.get('title') as string })\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"title\" required \u002F>\n      \u003Cbutton type=\"submit\" disabled={isPending}>\n        {isPending ? 'Adding...' : 'Add Todo'}\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n```\n\n**Why this works:**\n- Mutations use callbacks (`onSuccess`, `onError`, `onSettled`) - queries don't\n- `invalidateQueries` triggers background refetch\n- Mutations don't cache by default (correct behavior)\n\n---\n\n## The 7-Step Setup Process\n\n### Step 1: Install Dependencies\n\n```bash\n# Core library (required)\npnpm add @tanstack\u002Freact-query\n\n# DevTools (highly recommended for development)\npnpm add -D @tanstack\u002Freact-query-devtools\n\n# Optional: ESLint plugin for best practices\npnpm add -D @tanstack\u002Feslint-plugin-query\n```\n\n**Package roles:**\n- `@tanstack\u002Freact-query` - Core React hooks and QueryClient\n- `@tanstack\u002Freact-query-devtools` - Visual debugger (dev only, tree-shakeable)\n- `@tanstack\u002Feslint-plugin-query` - Catches common mistakes\n\n**Version requirements:**\n- React 18.0 or higher (uses `useSyncExternalStore`)\n- TypeScript 5.2+ for best type inference (optional but recommended)\n\n### Step 2: Configure QueryClient\n\n```tsx\n\u002F\u002F src\u002Flib\u002Fquery-client.ts\nimport { QueryClient } from '@tanstack\u002Freact-query'\n\nexport const queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      \u002F\u002F How long data is considered fresh (won't refetch during this time)\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n\n      \u002F\u002F How long inactive data stays in cache before garbage collection\n      gcTime: 1000 * 60 * 60, \u002F\u002F 1 hour (v5: renamed from cacheTime)\n\n      \u002F\u002F Retry failed requests (0 on server, 3 on client by default)\n      retry: (failureCount, error) => {\n        if (error instanceof Response && error.status === 404) return false\n        return failureCount \u003C 3\n      },\n\n      \u002F\u002F Refetch on window focus (can be annoying during dev)\n      refetchOnWindowFocus: false,\n\n      \u002F\u002F Refetch on network reconnect\n      refetchOnReconnect: true,\n\n      \u002F\u002F Refetch on component mount if data is stale\n      refetchOnMount: true,\n    },\n    mutations: {\n      \u002F\u002F Retry mutations on failure (usually don't want this)\n      retry: 0,\n    },\n  },\n})\n```\n\n**Key configuration decisions:**\n\n**staleTime vs gcTime:**\n- `staleTime`: How long until data is considered \"stale\" and might refetch\n  - `0` (default): Data is immediately stale, refetches on mount\u002Ffocus\n  - `1000 * 60 * 5`: Data fresh for 5 min, no refetch during this time\n  - `Infinity`: Data never stale, manual invalidation only\n- `gcTime`: How long unused data stays in cache\n  - `1000 * 60 * 5` (default): 5 minutes\n  - `Infinity`: Never garbage collect (memory leak risk)\n\n**When to refetch:**\n- `refetchOnWindowFocus: true` - Good for frequently changing data (stock prices)\n- `refetchOnWindowFocus: false` - Good for stable data or during development\n- `refetchOnMount: true` - Ensures fresh data when component mounts\n- `refetchOnReconnect: true` - Refetch after network reconnect\n\n### Step 3: Wrap App with Provider\n\n```tsx\n\u002F\u002F src\u002Fmain.tsx\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom\u002Fclient'\nimport { QueryClientProvider } from '@tanstack\u002Freact-query'\nimport { ReactQueryDevtools } from '@tanstack\u002Freact-query-devtools'\nimport { queryClient } from '.\u002Flib\u002Fquery-client'\nimport App from '.\u002FApp'\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003CStrictMode>\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CApp \u002F>\n      \u003CReactQueryDevtools\n        initialIsOpen={false}\n        buttonPosition=\"bottom-right\"\n      \u002F>\n    \u003C\u002FQueryClientProvider>\n  \u003C\u002FStrictMode>\n)\n```\n\n**Provider placement:**\n- Must wrap all components that use TanStack Query hooks\n- DevTools must be inside provider\n- Only one QueryClient instance for entire app\n\n**DevTools configuration:**\n- `initialIsOpen={false}` - Collapsed by default\n- `buttonPosition=\"bottom-right\"` - Where to show toggle button\n- Automatically removed in production builds (tree-shaken)\n\n### Advanced Setup (Steps 4-7)\n\n**For detailed patterns**: Load `references\u002Fadvanced-setup.md` when implementing custom query hooks, mutations with optimistic updates, DevTools configuration, or error boundaries.\n\n**Quick summaries:**\n\n**Step 4: Custom Query Hooks** - Use `queryOptions` factory for reusable patterns. Create custom hooks that encapsulate API calls.\n\n**Step 5: Mutations** - Use `useMutation` with `onSuccess` to invalidate queries. For instant UI feedback, implement optimistic updates with `onMutate`\u002F`onError`\u002F`onSettled` pattern.\n\n**Step 6: DevTools** - Already included in Step 3. Advanced options for customization available in reference.\n\n**Step 7: Error Boundaries** - Use `QueryErrorResetBoundary` with React Error Boundary. Configure `throwOnError` option for global vs local error handling.\n\n---\n\n## Critical Rules\n\n### Always Do\n\n✅ **Use object syntax for all hooks**\n```tsx\n\u002F\u002F v5 ONLY supports this:\nuseQuery({ queryKey, queryFn, ...options })\nuseMutation({ mutationFn, ...options })\n```\n\n✅ **Use array query keys**\n```tsx\nqueryKey: ['todos']              \u002F\u002F List\nqueryKey: ['todos', id]          \u002F\u002F Detail\nqueryKey: ['todos', { filter }]  \u002F\u002F Filtered\n```\n\n✅ **Configure staleTime appropriately**\n```tsx\nstaleTime: 1000 * 60 * 5 \u002F\u002F 5 min - prevents excessive refetches\n```\n\n✅ **Use isPending for initial loading state**\n```tsx\nif (isPending) return \u003CLoading \u002F>\n\u002F\u002F isPending = no data yet AND fetching\n```\n\n✅ **Throw errors in queryFn**\n```tsx\nif (!response.ok) throw new Error('Failed')\n```\n\n✅ **Invalidate queries after mutations**\n```tsx\nonSuccess: () => {\n  queryClient.invalidateQueries({ queryKey: ['todos'] })\n}\n```\n\n✅ **Use queryOptions factory for reusable patterns**\n```tsx\nconst opts = queryOptions({ queryKey, queryFn })\nuseQuery(opts)\nuseSuspenseQuery(opts)\nprefetchQuery(opts)\n```\n\n✅ **Use gcTime (not cacheTime)**\n```tsx\ngcTime: 1000 * 60 * 60 \u002F\u002F 1 hour\n```\n\n✅ **Know your status flags**\n```tsx\nisPending      \u002F\u002F no data yet, fetch in flight\nisFetching     \u002F\u002F any fetch in flight (including refetch)\nisRefetching   \u002F\u002F refetch specifically (data already cached)\nisLoadingError \u002F\u002F initial load failed\nisPaused       \u002F\u002F networkMode paused (e.g., offline)\nisFetchingNextPage \u002F\u002F useInfiniteQuery loading more\n```\n\n### Never Do\n\n❌ **Never use v4 array\u002Ffunction syntax**\n```tsx\n\u002F\u002F v4 (removed in v5):\nuseQuery(['todos'], fetchTodos, options) \u002F\u002F ❌\n\n\u002F\u002F v5 (correct):\nuseQuery({ queryKey: ['todos'], queryFn: fetchTodos }) \u002F\u002F ✅\n```\n\n❌ **Never use query callbacks (onSuccess, onError, onSettled in queries)**\n```tsx\n\u002F\u002F v5 removed these from queries:\nuseQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n  onSuccess: (data) => {}, \u002F\u002F ❌ Removed in v5\n})\n\n\u002F\u002F Use useEffect instead:\nconst { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })\nuseEffect(() => {\n  if (data) {\n    \u002F\u002F Do something\n  }\n}, [data])\n\n\u002F\u002F Or use mutation callbacks (still supported):\nuseMutation({\n  mutationFn: addTodo,\n  onSuccess: () => {}, \u002F\u002F ✅ Still works for mutations\n})\n```\n\n❌ **Never use deprecated options**\n```tsx\n\u002F\u002F Deprecated in v5:\ncacheTime: 1000 \u002F\u002F ❌ Use gcTime instead\nisLoading: true \u002F\u002F ❌ Meaning changed, use isPending\nkeepPreviousData: true \u002F\u002F ❌ Use placeholderData instead\nonSuccess: () => {} \u002F\u002F ❌ Removed from queries\nuseErrorBoundary: true \u002F\u002F ❌ Use throwOnError instead\n```\n\n❌ **Never assume isLoading means \"no data yet\"**\n```tsx\n\u002F\u002F v5 changed this:\nisLoading = isPending && isFetching \u002F\u002F ❌ Now means \"pending AND fetching\"\nisPending = no data yet \u002F\u002F ✅ Use this for initial load\n```\n\n❌ **Never forget initialPageParam for infinite queries**\n```tsx\n\u002F\u002F v5 requires this:\nuseInfiniteQuery({\n  queryKey: ['projects'],\n  queryFn: ({ pageParam }) => fetchProjects(pageParam),\n  initialPageParam: 0, \u002F\u002F ✅ Required in v5\n  getNextPageParam: (lastPage) => lastPage.nextCursor,\n})\n```\n\n❌ **Never use enabled with useSuspenseQuery**\n```tsx\n\u002F\u002F Not allowed:\nuseSuspenseQuery({\n  queryKey: ['todo', id],\n  queryFn: () => fetchTodo(id),\n  enabled: !!id, \u002F\u002F ❌ Not available with suspense\n})\n\n\u002F\u002F Use conditional rendering instead:\n{id && \u003CTodoComponent id={id} \u002F>}\n```\n\n---\n\n## Error Prevention\n\nThis skill prevents **8+ documented v5 migration issues**. The most critical errors include:\n- Object syntax required (v4 function overloads removed)\n- Query callbacks removed (onSuccess\u002FonError\u002FonSettled)\n- `isPending` vs `isLoading` status changes\n- `cacheTime` renamed to `gcTime`\n- `initialPageParam` required for infinite queries\n- `keepPreviousData` replaced with `placeholderData`\n\n**For complete error catalog with before\u002Fafter examples**: Load `references\u002Ftop-errors.md` when encountering errors or debugging v5 migration issues.\n\n---\n\n## Project Configuration\n\n**Essential configuration files**: package.json, tsconfig.json, .eslintrc.cjs\n\n**Key requirements**:\n- React 18.3.1+ (uses useSyncExternalStore)\n- TypeScript strict mode recommended\n- ESLint plugin catches v4→v5 migration errors\n\n**For complete configuration templates**: Load `references\u002Fconfiguration-files.md` when setting up new projects or troubleshooting build\u002Ftype errors.\n\n---\n\n## Common Patterns\n\n### Pattern 1: Dependent Queries\n\n```tsx\n\u002F\u002F Fetch user, then fetch user's posts\nfunction UserPosts({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  const { data: posts } = useQuery({\n    queryKey: ['users', userId, 'posts'],\n    queryFn: () => fetchUserPosts(userId),\n    enabled: !!user, \u002F\u002F Only fetch posts after user is loaded\n  })\n\n  if (!user) return \u003Cdiv>Loading user...\u003C\u002Fdiv>\n  if (!posts) return \u003Cdiv>Loading posts...\u003C\u002Fdiv>\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>{user.name}\u003C\u002Fh1>\n      \u003Cul>\n        {posts.map(post => (\n          \u003Cli key={post.id}>{post.title}\u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**When to use**: Query B depends on data from Query A\n\n### Pattern 2: Parallel Queries with useQueries\n\n```tsx\n\u002F\u002F Fetch multiple todos in parallel\nfunction TodoDetails({ ids }: { ids: number[] }) {\n  const results = useQueries({\n    queries: ids.map(id => ({\n      queryKey: ['todos', id],\n      queryFn: () => fetchTodo(id),\n    })),\n  })\n\n  const isLoading = results.some(result => result.isPending)\n  const isError = results.some(result => result.isError)\n\n  if (isLoading) return \u003Cdiv>Loading...\u003C\u002Fdiv>\n  if (isError) return \u003Cdiv>Error loading todos\u003C\u002Fdiv>\n\n  return (\n    \u003Cul>\n      {results.map((result, i) => (\n        \u003Cli key={ids[i]}>{result.data?.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**When to use**: Fetch multiple independent queries in parallel\n\n### Pattern 3: Prefetching\n\n```tsx\nimport { useQueryClient } from '@tanstack\u002Freact-query'\nimport { todosQueryOptions } from '.\u002Fhooks\u002FuseTodos'\n\nfunction TodoListWithPrefetch() {\n  const queryClient = useQueryClient()\n  const { data: todos } = useTodos()\n\n  const prefetchTodo = (id: number) => {\n    queryClient.prefetchQuery({\n      queryKey: ['todos', id],\n      queryFn: () => fetchTodo(id),\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n    })\n  }\n\n  return (\n    \u003Cul>\n      {todos?.map(todo => (\n        \u003Cli\n          key={todo.id}\n          onMouseEnter={() => prefetchTodo(todo.id)}\n        >\n          \u003CLink to={`\u002Ftodos\u002F${todo.id}`}>{todo.title}\u003C\u002FLink>\n        \u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**When to use**: Preload data before user navigates (on hover, on mount)\n\n### Pattern 4: Infinite Scroll\n\n```tsx\nimport { useInfiniteQuery } from '@tanstack\u002Freact-query'\nimport { useEffect, useRef } from 'react'\n\ntype Page = {\n  data: Todo[]\n  nextCursor: number | null\n}\n\nasync function fetchTodosPage({ pageParam }: { pageParam: number }): Promise\u003CPage> {\n  const response = await fetch(`\u002Fapi\u002Ftodos?cursor=${pageParam}&limit=20`)\n  return response.json()\n}\n\nfunction InfiniteTodoList() {\n  const {\n    data,\n    fetchNextPage,\n    hasNextPage,\n    isFetchingNextPage,\n  } = useInfiniteQuery({\n    queryKey: ['todos', 'infinite'],\n    queryFn: fetchTodosPage,\n    initialPageParam: 0,\n    getNextPageParam: (lastPage) => lastPage.nextCursor,\n  })\n\n  const loadMoreRef = useRef\u003CHTMLDivElement>(null)\n\n  useEffect(() => {\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries[0].isIntersecting && hasNextPage) {\n          fetchNextPage()\n        }\n      },\n      { threshold: 0.1 }\n    )\n\n    if (loadMoreRef.current) {\n      observer.observe(loadMoreRef.current)\n    }\n\n    return () => observer.disconnect()\n  }, [fetchNextPage, hasNextPage])\n\n  return (\n    \u003Cdiv>\n      {data?.pages.map((page, i) => (\n        \u003Cdiv key={i}>\n          {page.data.map(todo => (\n            \u003Cdiv key={todo.id}>{todo.title}\u003C\u002Fdiv>\n          ))}\n        \u003C\u002Fdiv>\n      ))}\n\n      \u003Cdiv ref={loadMoreRef}>\n        {isFetchingNextPage && \u003Cdiv>Loading more...\u003C\u002Fdiv>}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**When to use**: Paginated lists with infinite scroll\n\n### Pattern 5: Query Cancellation\n\n```tsx\nfunction SearchTodos() {\n  const [search, setSearch] = useState('')\n\n  const { data } = useQuery({\n    queryKey: ['todos', 'search', search],\n    queryFn: async ({ signal }) => {\n      const response = await fetch(`\u002Fapi\u002Ftodos?q=${search}`, { signal })\n      return response.json()\n    },\n    enabled: search.length > 2, \u002F\u002F Only search if 3+ characters\n  })\n\n  return (\n    \u003Cdiv>\n      \u003Cinput\n        value={search}\n        onChange={e => setSearch(e.target.value)}\n        placeholder=\"Search todos...\"\n      \u002F>\n      {data && (\n        \u003Cul>\n          {data.map(todo => (\n            \u003Cli key={todo.id}>{todo.title}\u003C\u002Fli>\n          ))}\n        \u003C\u002Ful>\n      )}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n**How it works:**\n- When queryKey changes, previous query is automatically cancelled\n- Pass `signal` to fetch for proper cleanup\n- Browser aborts pending fetch requests\n\n### Pattern 6: Background Fetch Indicators\n\n```tsx\nconst { data, isFetching, isRefetching } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n  staleTime: 1000 * 60 * 5,\n})\n\nreturn (\n  \u003Cdiv>\n    {isFetching && \u003CSpinner label={isRefetching ? 'Refreshing…' : 'Loading…'} \u002F>}\n    \u003CTodoList data={data} \u002F>\n  \u003C\u002Fdiv>\n)\n```\n\n**Why:** `isFetching` stays true during background refetches so you can show a subtle \"Refreshing\" badge without losing cached data.\n\n---\n\n## Using Bundled Resources\n\n### Templates (templates\u002F)\n\nComplete, copy-ready code examples:\n\n- `package.json` - Dependencies with exact versions\n- `query-client-config.ts` - QueryClient setup with best practices\n- `provider-setup.tsx` - App wrapper with QueryClientProvider\n- `use-query-basic.tsx` - Basic useQuery hook pattern\n- `use-mutation-basic.tsx` - Basic useMutation hook\n- `use-mutation-optimistic.tsx` - Optimistic update pattern\n- `use-infinite-query.tsx` - Infinite scroll pattern\n- `custom-hooks-pattern.tsx` - Reusable query hooks with queryOptions\n- `error-boundary.tsx` - Error boundary with query reset\n- `devtools-setup.tsx` - DevTools configuration\n\n**Example Usage:**\n```bash\n# Copy query client config\ncp ~\u002F.claude\u002Fskills\u002Ftanstack-query\u002Ftemplates\u002Fquery-client-config.ts src\u002Flib\u002F\n\n# Copy provider setup\ncp ~\u002F.claude\u002Fskills\u002Ftanstack-query\u002Ftemplates\u002Fprovider-setup.tsx src\u002Fmain.tsx\n\n# Or run the bootstrap helper (installs deps + copies core files):\n.\u002Fscripts\u002Fexample-script.sh . pnpm\n```\n\n### References (references\u002F)\n\nDeep-dive documentation loaded when needed:\n\n- `advanced-setup.md` - Custom hooks, mutations, optimistic updates, DevTools, error boundaries\n- `configuration-files.md` - Complete package.json, tsconfig.json, .eslintrc.cjs templates\n- `v4-to-v5-migration.md` - Complete v4 → v5 migration guide\n- `best-practices.md` - Request waterfalls, caching strategies, performance\n- `common-patterns.md` - Reusable queries, optimistic updates, infinite scroll\n- `official-guides-map.md` - When to open each official doc and what it covers\n- `typescript-patterns.md` - Type safety, generics, type inference\n- `testing.md` - Testing with MSW, React Testing Library\n- `top-errors.md` - All 8+ errors with solutions\n\n### Examples (examples\u002F)\n\n- `examples\u002FREADME.md` - Index of top 10 scenarios with official links\n- `basic.tsx` - Minimal list query\n- `basic-graphql-request.tsx` - GraphQL client + select\n- `optimistic-update.tsx` - onMutate snapshot\u002Frollback\n- `pagination.tsx` - paginated list with placeholderData\n- `infinite-scroll.tsx` - useInfiniteQuery + IntersectionObserver\n- `prefetching.tsx` - prefetch on hover before navigation\n- `suspense.tsx` - useSuspenseQuery + boundary\n- `default-query-function.ts` - global fetcher using queryKey\n- `nextjs-app-router.tsx` - App Router prefetch + hydrate (`networkMode: 'always'`)\n- `react-native.tsx` - offline-first with AsyncStorage persister\n\n**When Claude should load these:**\n- `advanced-setup.md` - When implementing custom query hooks, mutations, or error boundaries\n- `configuration-files.md` - When setting up new projects or troubleshooting build\u002Ftype errors\n- `v4-to-v5-migration.md` - When migrating existing React Query v4 project\n- `best-practices.md` - When optimizing performance or avoiding waterfalls\n- `common-patterns.md` - When implementing specific features (infinite scroll, etc.)\n- `typescript-patterns.md` - When dealing with TypeScript errors or type inference\n- `testing.md` - When writing tests for components using TanStack Query\n- `top-errors.md` - When encountering errors not covered in main SKILL.md\n\n---\n\n## Advanced Topics\n\n### Data Transformations with select\n\n```tsx\n\u002F\u002F Only subscribe to specific slice of data\nfunction TodoCount() {\n  const { data: count } = useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n    select: (data) => data.length, \u002F\u002F Only re-render when count changes\n  })\n\n  return \u003Cdiv>Total todos: {count}\u003C\u002Fdiv>\n}\n\n\u002F\u002F Transform data shape\nfunction CompletedTodoTitles() {\n  const { data: titles } = useQuery({\n    queryKey: ['todos'],\n    queryFn: fetchTodos,\n    select: (data) =>\n      data\n        .filter(todo => todo.completed)\n        .map(todo => todo.title),\n  })\n\n  return (\n    \u003Cul>\n      {titles?.map((title, i) => (\n        \u003Cli key={i}>{title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n```\n\n**Benefits:**\n- Component only re-renders when selected data changes\n- Reduces memory usage (less data stored in component state)\n- Keeps query cache unchanged (other components get full data)\n\n### Request Waterfalls (Anti-Pattern)\n\n```tsx\n\u002F\u002F ❌ BAD: Sequential waterfalls\nfunction BadUserProfile({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  const { data: posts } = useQuery({\n    queryKey: ['posts', user?.id],\n    queryFn: () => fetchPosts(user!.id),\n    enabled: !!user,\n  })\n\n  const { data: comments } = useQuery({\n    queryKey: ['comments', posts?.[0]?.id],\n    queryFn: () => fetchComments(posts![0].id),\n    enabled: !!posts && posts.length > 0,\n  })\n\n  \u002F\u002F Each query waits for previous one = slow!\n}\n\n\u002F\u002F ✅ GOOD: Fetch in parallel when possible\nfunction GoodUserProfile({ userId }: { userId: number }) {\n  const { data: user } = useQuery({\n    queryKey: ['users', userId],\n    queryFn: () => fetchUser(userId),\n  })\n\n  \u002F\u002F Fetch posts AND comments in parallel\n  const { data: posts } = useQuery({\n    queryKey: ['posts', userId],\n    queryFn: () => fetchPosts(userId), \u002F\u002F Don't wait for user\n  })\n\n  const { data: comments } = useQuery({\n    queryKey: ['comments', userId],\n    queryFn: () => fetchUserComments(userId), \u002F\u002F Don't wait for posts\n  })\n\n  \u002F\u002F All 3 queries run in parallel = fast!\n}\n```\n\n### Server State vs Client State\n\n```tsx\n\u002F\u002F ❌ Don't use TanStack Query for client-only state\nconst { data: isModalOpen, setData: setIsModalOpen } = useMutation(...)\n\n\u002F\u002F ✅ Use useState for client state\nconst [isModalOpen, setIsModalOpen] = useState(false)\n\n\u002F\u002F ✅ Use TanStack Query for server state\nconst { data: todos } = useQuery({\n  queryKey: ['todos'],\n  queryFn: fetchTodos,\n})\n```\n\n**Rule of thumb:**\n- Server state: Use TanStack Query (data from API)\n- Client state: Use useState\u002FuseReducer (local UI state)\n- Global client state: Use Zustand\u002FContext (theme, auth token)\n\n---\n\n## Platform & Integration Notes\n\n- **React Native**: Works the same as web. Use `@tanstack\u002Fquery-async-storage-persister` to persist cache to AsyncStorage; avoid window-focus refetch logic. DevTools panel not available natively—use Flipper or expose logs.\n- **GraphQL**: Pair with `graphql-request` or urql's bare client. Treat operations as plain async functions; co-locate fragments and use `select` to map edges\u002Fnodes to flat shapes.\n- **SSR \u002F Next.js \u002F TanStack Start**: Use `dehydrate`\u002F`HydrationBoundary` on the server and `QueryClientProvider` on the client. Set `networkMode: 'always'` for server prefetches so requests are never paused.\n- **Suspense**: Prefer `useSuspenseQuery` for routes already using Suspense. Do not combine with `enabled`; gate rendering instead.\n- **Testing**: Use `@testing-library\u002Freact` + `@tanstack\u002Freact-query\u002Ftesting` helpers and mock network with MSW. Reset QueryClient between tests to avoid cache bleed.\n\n---\n\n## Dependencies\n\n**Required**:\n- `@tanstack\u002Freact-query@5.90.12` - Core library\n- `react@18.0.0+` - Uses useSyncExternalStore hook\n- `react-dom@18.0.0+` - React DOM renderer\n\n**Recommended**:\n- `@tanstack\u002Freact-query-devtools@5.91.1` - Visual debugger (dev only)\n- `@tanstack\u002Feslint-plugin-query@5.91.2` - ESLint rules for best practices\n- `typescript@5.2.0+` - For type safety and inference\n\n**Optional**:\n- `@tanstack\u002Fquery-sync-storage-persister` - Persist cache to localStorage\n- `@tanstack\u002Fquery-async-storage-persister` - Persist to AsyncStorage (React Native)\n\n---\n\n## Official Documentation\n\n- **TanStack Query Docs**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\n- **React Integration**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Foverview\n- **v5 Migration Guide**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Fguides\u002Fmigrating-to-v5\n- **API Reference**: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\u002Fdocs\u002Fframework\u002Freact\u002Freference\u002FuseQuery\n- **Context7 Library ID**: `\u002Fwebsites\u002Ftanstack_query`\n- **GitHub Repository**: https:\u002F\u002Fgithub.com\u002FTanStack\u002Fquery\n- **Discord Community**: https:\u002F\u002Ftlinz.com\u002Fdiscord\n\n---\n\n## Package Versions (Verified 2025-12-09)\n\n```json\n{\n  \"dependencies\": {\n    \"@tanstack\u002Freact-query\": \"^5.90.12\"\n  },\n  \"devDependencies\": {\n    \"@tanstack\u002Freact-query-devtools\": \"^5.91.1\",\n    \"@tanstack\u002Feslint-plugin-query\": \"^5.91.2\"\n  }\n}\n```\n\n**Verification:**\n- `npm view @tanstack\u002Freact-query version` → 5.90.12\n- `npm view @tanstack\u002Freact-query-devtools version` → 5.91.1\n- `npm view @tanstack\u002Feslint-plugin-query version` → 5.91.2\n- Last checked: 2025-12-09\n\n---\n\n## Production Example\n\nThis skill is based on production patterns used in:\n- **Build Time**: ~6 hours research + development\n- **Errors Prevented**: 8 (all documented v5 migration issues)\n- **Token Efficiency**: ~65% savings vs manual setup\n- **Validation**: ✅ All patterns tested with TypeScript strict mode\n\n---\n\n## Troubleshooting\n\n### Problem: \"useQuery is not a function\" or type errors\n**Solution**: Ensure you're using v5 object syntax:\n```tsx\n\u002F\u002F ✅ Correct:\nuseQuery({ queryKey: ['todos'], queryFn: fetchTodos })\n\n\u002F\u002F ❌ Wrong (v4 syntax):\nuseQuery(['todos'], fetchTodos)\n```\n\n### Problem: Callbacks (onSuccess, onError) not working on queries\n**Solution**: Removed in v5. Use `useEffect` or move to mutations:\n```tsx\n\u002F\u002F ✅ For queries:\nconst { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })\nuseEffect(() => {\n  if (data) {\n    \u002F\u002F Handle success\n  }\n}, [data])\n\n\u002F\u002F ✅ For mutations (still work):\nuseMutation({\n  mutationFn: addTodo,\n  onSuccess: () => { \u002F* ... *\u002F },\n})\n```\n\n### Problem: isLoading always false even during initial load\n**Solution**: Use `isPending` instead:\n```tsx\nconst { isPending, isLoading, isFetching } = useQuery(...)\n\u002F\u002F isPending = no data yet\n\u002F\u002F isLoading = isPending && isFetching\n\u002F\u002F isFetching = any fetch in progress\n```\n\n### Problem: cacheTime option not recognized\n**Solution**: Renamed to `gcTime` in v5:\n```tsx\ngcTime: 1000 * 60 * 60 \u002F\u002F 1 hour\n```\n\n### Problem: useSuspenseQuery with enabled option gives type error\n**Solution**: `enabled` not available with suspense. Use conditional rendering:\n```tsx\n{id && \u003CTodoComponent id={id} \u002F>}\n```\n\n### Problem: Data not refetching after mutation\n**Solution**: Invalidate queries in `onSuccess`:\n```tsx\nonSuccess: () => {\n  queryClient.invalidateQueries({ queryKey: ['todos'] })\n}\n```\n\n### Problem: Infinite query requires initialPageParam\n**Solution**: Always provide `initialPageParam` in v5:\n```tsx\nuseInfiniteQuery({\n  queryKey: ['projects'],\n  queryFn: ({ pageParam }) => fetchProjects(pageParam),\n  initialPageParam: 0, \u002F\u002F Required\n  getNextPageParam: (lastPage) => lastPage.nextCursor,\n})\n```\n\n### Problem: keepPreviousData not working\n**Solution**: Replaced with `placeholderData`:\n```tsx\nimport { keepPreviousData } from '@tanstack\u002Freact-query'\n\nuseQuery({\n  queryKey: ['todos', page],\n  queryFn: () => fetchTodos(page),\n  placeholderData: keepPreviousData,\n})\n```\n\n---\n\n## Complete Setup Checklist\n\nUse this checklist to verify your setup:\n\n- [ ] Installed @tanstack\u002Freact-query@5.90.12+\n- [ ] Installed @tanstack\u002Freact-query-devtools (dev dependency)\n- [ ] Created QueryClient with configured defaults\n- [ ] Wrapped app with QueryClientProvider\n- [ ] Added ReactQueryDevtools component\n- [ ] Created first query using object syntax\n- [ ] Tested isPending and error states\n- [ ] Created first mutation with onSuccess handler\n- [ ] Set up query invalidation after mutations\n- [ ] Configured staleTime and gcTime appropriately\n- [ ] Using array queryKey consistently\n- [ ] Throwing errors in queryFn\n- [ ] No v4 syntax (function overloads)\n- [ ] No query callbacks (onSuccess, onError on queries)\n- [ ] Using isPending (not isLoading) for initial load\n- [ ] DevTools working in development\n- [ ] TypeScript types working correctly\n- [ ] Production build succeeds\n\n---\n\n**Questions? Issues?**\n\n1. Check `references\u002Ftop-errors.md` for complete error solutions\n2. Verify all steps in the setup process\n3. Check official docs: https:\u002F\u002Ftanstack.com\u002Fquery\u002Flatest\n4. Ensure using v5 syntax (object syntax, gcTime, isPending)\n5. Join Discord: https:\u002F\u002Ftlinz.com\u002Fdiscord\n",{},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":7,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill tanstack-query",{"tool":41,"command":42},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill tanstack-query",{"tool":44,"command":45},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill tanstack-query","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002FSKILL.md",true,[49,55,61,65,71,75,79,83,87,91,95,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173,177,181,185],{"path":50,"resourceType":51,"resourceTypeDesc":52,"sizeBytes":53,"sourceHtmlUrl":54},"assets\u002Fexample-template.txt","ASSET","资产文件",416,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fassets\u002Fexample-template.txt",{"path":56,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":59,"sourceHtmlUrl":60},"examples\u002Fbasic-graphql-request.tsx","OTHER","其他",759,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fbasic-graphql-request.tsx",{"path":62,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":63,"sourceHtmlUrl":64},"examples\u002Fbasic.tsx",663,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fbasic.tsx",{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"examples\u002Fdefault-query-function.ts","SCRIPT","脚本",1017,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fdefault-query-function.ts",{"path":72,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":73,"sourceHtmlUrl":74},"examples\u002Finfinite-scroll.tsx",1333,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Finfinite-scroll.tsx",{"path":76,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":77,"sourceHtmlUrl":78},"examples\u002Fnextjs-app-router.tsx",1128,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fnextjs-app-router.tsx",{"path":80,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":81,"sourceHtmlUrl":82},"examples\u002Foptimistic-update.tsx",1976,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Foptimistic-update.tsx",{"path":84,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":85,"sourceHtmlUrl":86},"examples\u002Fpagination.tsx",1058,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fpagination.tsx",{"path":88,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":89,"sourceHtmlUrl":90},"examples\u002Fprefetching.tsx",848,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fprefetching.tsx",{"path":92,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":93,"sourceHtmlUrl":94},"examples\u002Freact-native.tsx",1762,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Freact-native.tsx",{"path":96,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":99,"sourceHtmlUrl":100},"examples\u002FREADME.md","REFERENCE","参考文档",1855,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002FREADME.md",{"path":102,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":103,"sourceHtmlUrl":104},"examples\u002Fsuspense.tsx",700,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fexamples\u002Fsuspense.tsx",{"path":106,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":107,"sourceHtmlUrl":108},"references\u002Fadvanced-setup.md",14449,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fadvanced-setup.md",{"path":110,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":111,"sourceHtmlUrl":112},"references\u002Fbest-practices.md",6188,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fbest-practices.md",{"path":114,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":115,"sourceHtmlUrl":116},"references\u002Fcommon-patterns.md",5425,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fcommon-patterns.md",{"path":118,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":119,"sourceHtmlUrl":120},"references\u002Fconfiguration-files.md",5964,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fconfiguration-files.md",{"path":122,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":123,"sourceHtmlUrl":124},"references\u002Fexample-reference.md",760,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fexample-reference.md",{"path":126,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":127,"sourceHtmlUrl":128},"references\u002Fofficial-guides-map.md",4650,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fofficial-guides-map.md",{"path":130,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":131,"sourceHtmlUrl":132},"references\u002Ftesting.md",6263,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Ftesting.md",{"path":134,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":135,"sourceHtmlUrl":136},"references\u002Ftop-errors.md",6771,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Ftop-errors.md",{"path":138,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":139,"sourceHtmlUrl":140},"references\u002Ftypescript-patterns.md",5466,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Ftypescript-patterns.md",{"path":142,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":143,"sourceHtmlUrl":144},"references\u002Fv4-to-v5-migration.md",4441,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Freferences\u002Fv4-to-v5-migration.md",{"path":146,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":147,"sourceHtmlUrl":148},"scripts\u002Fexample-script.sh",2293,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Fscripts\u002Fexample-script.sh",{"path":150,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":151,"sourceHtmlUrl":152},"templates\u002Fcustom-hooks-pattern.tsx",7353,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fcustom-hooks-pattern.tsx",{"path":154,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":155,"sourceHtmlUrl":156},"templates\u002Fdevtools-setup.tsx",6857,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fdevtools-setup.tsx",{"path":158,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":159,"sourceHtmlUrl":160},"templates\u002Ferror-boundary.tsx",6335,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Ferror-boundary.tsx",{"path":162,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":163,"sourceHtmlUrl":164},"templates\u002Fpackage.json",836,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fpackage.json",{"path":166,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":167,"sourceHtmlUrl":168},"templates\u002Fprovider-setup.tsx",1571,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fprovider-setup.tsx",{"path":170,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":171,"sourceHtmlUrl":172},"templates\u002Fquery-client-config.ts",2181,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fquery-client-config.ts",{"path":174,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":175,"sourceHtmlUrl":176},"templates\u002Fuse-infinite-query.tsx",5317,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fuse-infinite-query.tsx",{"path":178,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":179,"sourceHtmlUrl":180},"templates\u002Fuse-mutation-basic.tsx",4694,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fuse-mutation-basic.tsx",{"path":182,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":183,"sourceHtmlUrl":184},"templates\u002Fuse-mutation-optimistic.tsx",6432,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fuse-mutation-optimistic.tsx",{"path":186,"resourceType":57,"resourceTypeDesc":58,"sizeBytes":187,"sourceHtmlUrl":188},"templates\u002Fuse-query-basic.tsx",2594,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fsecondsky\u002Fclaude-skills\u002Ftanstack-query\u002Ftemplates\u002Fuse-query-basic.tsx",[190,204,214,223,229,236],{"skillNo":191,"name":192,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":193,"categories":194,"keywords":200,"publishedAt":21},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[195],{"categoryNo":196,"categoryName":197,"taxonomyType":198,"taxonomyTypeDesc":199},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[201,202,203],"model","model hierarchy","model hierarchy skill",{"skillNo":205,"name":206,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":210,"publishedAt":21},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[209],{"categoryNo":196,"categoryName":197,"taxonomyType":198,"taxonomyTypeDesc":199},[211,212,213],"MCP","zoom","zoom mcp",{"skillNo":215,"name":216,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":217,"categories":218,"keywords":219,"publishedAt":21},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[220,221,222,212],"sdk","zoom apps sdk","apps",{"skillNo":224,"name":225,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":226,"categories":227,"keywords":228,"publishedAt":21},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[225],{"skillNo":230,"name":231,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":232,"categories":233,"keywords":235,"publishedAt":21},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[234],{"categoryNo":196,"categoryName":197,"taxonomyType":198,"taxonomyTypeDesc":199},[231],{"skillNo":237,"name":238,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":239,"categories":240,"keywords":241,"publishedAt":21},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[238]]