Agent skill

Tanstack Query

by secondsky in secondsky/claude-skills

TanStack Query v5 (React Query) server state management. An agent skill from secondsky/claude-skills.

MITAuto-check passedFrontend & Design

Install Tanstack Query

skills CLI
$ npx skills add secondsky/claude-skills --skill tanstack-query -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install secondsky/claude-skills tanstack-query --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/secondsky/claude-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/tanstack-query/skills/tanstack-query .claude/skills/tanstack-query && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
tanstack-query
GitHub stars
227
Token cost
~7.9k tokens
SKILL.md length
1,612 words
Files
36 (incl. scripts, references, assets)
Skills in repo
169
Repo updated
First seen
Licence
MIT

At a glance

TanStack Query v5 (React Query) server state management. An agent skill from secondsky/claude-skills.

  • Works in 7 steps: Install Dependencies → Set Up QueryClient Provider → Create First Query → …
  • Encountering v4 migration
  • SKILL.md covers Quick Start (5 Minutes), The 7-Step Setup Process, Critical Rules and Error Prevention, plus 4 more sections
  • Calls pnpm, npm and bun

What it does

Tanstack Query is an agent skill from secondsky/claude-skills. TanStack Query v5 (React Query) server state management. Use for data fetching, caching, mutations, or encountering v4 migration, stale data, invalidation errors.

Its SKILL.md is about 7.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 38 other files, including scripts, reference files and assets (for example `examples/README.md`, `references/advanced-setup.md` and `references/best-practices.md`).

It sits in Frontend & Design, covering State management and Caching. It works with TanStack and React. The repository describes itself as: Production-ready skills for Claude Code CLI - Cloudflare, React, Tailwind v4, and AI integrations. The licence is MIT.

When your agent uses it

  • Encountering v4 migration
  • Invalidation errors

Example prompts

  • “/tanstack-query”

Requirements

  • Node.js

Workflow steps

7 steps, taken from the step headings in SKILL.md.

  1. Install Dependencies
  2. Set Up QueryClient Provider
  3. Create First Query
  4. Create First Mutation
  5. Install Dependencies
  6. Configure QueryClient
  7. Wrap App with Provider

What it can do on your machine

Read from SKILL.md and the folder at commit 8837836. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 1 file in scripts/, which the agent can run.

    Shell commands in SKILL.md call:

    • pnpm
    • npm
    • bun

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • tanstack.com
    • tlinz.com
    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Tanstack Query loads about 7.9k tokens when it runs, and up to ~23k if it reads all its reference files. Until then it costs about 44 tokens; SKILL.md has 1,612 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~44
When it runs · the whole SKILL.md, loaded when a task matches
~7.9k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~23k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from secondsky/claude-skills at commit 8837836, republished under its MIT licence (© secondsky). 1,612 words, ~7,896 tokens.

Download SKILL.mdSave it as .claude/skills/tanstack-query/SKILL.md (or your agent's skills folder). This skill also uses 35 other files; get the full folder from GitHub.
name
tanstack-query
description
TanStack Query v5 (React Query) server state management. Use for data fetching, caching, mutations, or encountering v4 migration, stale data, invalidation errors.
metadata.keywords
TanStack Query, React Query, useQuery, useMutation, useInfiniteQuery, useSuspenseQuery, QueryClient, QueryClientProvider, data fetching, server state…
license
MIT

TanStack Query (React Query) v5

Status: Production Ready ✅ Last Updated: 2026-08-03 Dependencies: React 18.0+ (18.3+ recommended), TypeScript 4.9+ (5.x preferred) Latest Versions: @tanstack/react-query@5.101.4, @tanstack/react-query-devtools@5.101.4, @tanstack/eslint-plugin-query@5.101.4


Quick Start (5 Minutes)

1. Install Dependencies
bash
# choose your package manager
pnpm add @tanstack/react-query@latest @tanstack/react-query-devtools@latest
# or
npm install @tanstack/react-query@latest @tanstack/react-query-devtools@latest
# or
bun add @tanstack/react-query@latest @tanstack/react-query-devtools@latest

Why this matters:

  • TanStack Query v5 requires React 18+ (uses useSyncExternalStore)
  • DevTools are essential for debugging queries and mutations
  • v5 has breaking changes from v4 - use latest for all fixes
2. Set Up QueryClient Provider
tsx
// src/main.tsx or src/index.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import App from './App'

// Create a client
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
      gcTime: 1000 * 60 * 60, // 1 hour (formerly cacheTime)
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
})

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </StrictMode>
)

CRITICAL:

  • Wrap entire app with QueryClientProvider
  • Configure staleTime to avoid excessive refetches (default is 0)
  • Use gcTime (not cacheTime - renamed in v5)
  • DevTools should be inside provider

Know the defaults (v5):

  • staleTime: 0 → data is immediately stale, so refetches on mount/focus unless you raise it
  • gcTime: 5 * 60 * 1000 → inactive data is garbage-collected after 5 minutes
  • retry: 3 in browsers, retry: 0 on the server
  • refetchOnWindowFocus: true and refetchOnReconnect: true
  • networkMode: 'online' (requests pause while offline). Switch to 'always' for SSR/prefetch where you don't want cancellation. citeturn1search0turn1search1
3. Create First Query
tsx
// src/hooks/useTodos.ts
import { useQuery } from '@tanstack/react-query'

type Todo = {
  id: number
  title: string
  completed: boolean
}

async function fetchTodos(): Promise<Todo[]> {
  const response = await fetch('/api/todos')
  if (!response.ok) {
    throw new Error('Failed to fetch todos')
  }
  return response.json()
}

export function useTodos() {
  return useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })
}

// Usage in component:
function TodoList() {
  const { data, isPending, isError, error } = useTodos()

  if (isPending) return <div>Loading...</div>
  if (isError) return <div>Error: {error.message}</div>

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

CRITICAL:

  • v5 requires object syntax: useQuery({ queryKey, queryFn })
  • Use isPending (not isLoading - that now means "pending AND fetching")
  • Always throw errors in queryFn for proper error handling
  • QueryKey should be array for consistent cache keys
4. Create First Mutation
tsx
// src/hooks/useAddTodo.ts
import { useMutation, useQueryClient } from '@tanstack/react-query'

type NewTodo = {
  title: string
}

async function addTodo(newTodo: NewTodo) {
  const response = await fetch('/api/todos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newTodo),
  })
  if (!response.ok) throw new Error('Failed to add todo')
  return response.json()
}

export function useAddTodo() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: addTodo,
    onSuccess: () => {
      // Invalidate and refetch todos
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })
}

// Usage in component:
function AddTodoForm() {
  const { mutate, isPending } = useAddTodo()

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    mutate({ title: formData.get('title') as string })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" required />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Adding...' : 'Add Todo'}
      </button>
    </form>
  )
}

Why this works:

  • Mutations use callbacks (onSuccess, onError, onSettled) - queries don't
  • invalidateQueries triggers background refetch
  • Mutations don't cache by default (correct behavior)

The 7-Step Setup Process

Step 1: Install Dependencies
bash
# Core library (required)
pnpm add @tanstack/react-query

# DevTools (highly recommended for development)
pnpm add -D @tanstack/react-query-devtools

# Optional: ESLint plugin for best practices
pnpm add -D @tanstack/eslint-plugin-query

Package roles:

  • @tanstack/react-query - Core React hooks and QueryClient
  • @tanstack/react-query-devtools - Visual debugger (dev only, tree-shakeable)
  • @tanstack/eslint-plugin-query - Catches common mistakes

Version requirements:

  • React 18.0 or higher (uses useSyncExternalStore)
  • TypeScript 5.2+ for best type inference (optional but recommended)
Step 2: Configure QueryClient
tsx
// src/lib/query-client.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // How long data is considered fresh (won't refetch during this time)
      staleTime: 1000 * 60 * 5, // 5 minutes

      // How long inactive data stays in cache before garbage collection
      gcTime: 1000 * 60 * 60, // 1 hour (v5: renamed from cacheTime)

      // Retry failed requests (0 on server, 3 on client by default)
      retry: (failureCount, error) => {
        if (error instanceof Response && error.status === 404) return false
        return failureCount < 3
      },

      // Refetch on window focus (can be annoying during dev)
      refetchOnWindowFocus: false,

      // Refetch on network reconnect
      refetchOnReconnect: true,

      // Refetch on component mount if data is stale
      refetchOnMount: true,
    },
    mutations: {
      // Retry mutations on failure (usually don't want this)
      retry: 0,
    },
  },
})

Key configuration decisions:

staleTime vs gcTime:

  • staleTime: How long until data is considered "stale" and might refetch
    • 0 (default): Data is immediately stale, refetches on mount/focus
    • 1000 * 60 * 5: Data fresh for 5 min, no refetch during this time
    • Infinity: Data never stale, manual invalidation only
  • gcTime: How long unused data stays in cache
    • 1000 * 60 * 5 (default): 5 minutes
    • Infinity: Never garbage collect (memory leak risk)

When to refetch:

  • refetchOnWindowFocus: true - Good for frequently changing data (stock prices)
  • refetchOnWindowFocus: false - Good for stable data or during development
  • refetchOnMount: true - Ensures fresh data when component mounts
  • refetchOnReconnect: true - Refetch after network reconnect
Step 3: Wrap App with Provider
tsx
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { queryClient } from './lib/query-client'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools
        initialIsOpen={false}
        buttonPosition="bottom-right"
      />
    </QueryClientProvider>
  </StrictMode>
)

Provider placement:

  • Must wrap all components that use TanStack Query hooks
  • DevTools must be inside provider
  • Only one QueryClient instance for entire app

DevTools configuration:

  • initialIsOpen={false} - Collapsed by default
  • buttonPosition="bottom-right" - Where to show toggle button
  • Automatically removed in production builds (tree-shaken)
Advanced Setup (Steps 4-7)

For detailed patterns: Load references/advanced-setup.md when implementing custom query hooks, mutations with optimistic updates, DevTools configuration, or error boundaries.

Quick summaries:

Step 4: Custom Query Hooks - Use queryOptions factory for reusable patterns. Create custom hooks that encapsulate API calls.

Step 5: Mutations - Use useMutation with onSuccess to invalidate queries. For instant UI feedback, implement optimistic updates with onMutate/onError/onSettled pattern.

Step 6: DevTools - Already included in Step 3. Advanced options for customization available in reference.

Step 7: Error Boundaries - Use QueryErrorResetBoundary with React Error Boundary. Configure throwOnError option for global vs local error handling.


Critical Rules

Always Do

✅ Use object syntax for all hooks

tsx
// v5 ONLY supports this:
useQuery({ queryKey, queryFn, ...options })
useMutation({ mutationFn, ...options })

✅ Use array query keys

tsx
queryKey: ['todos']              // List
queryKey: ['todos', id]          // Detail
queryKey: ['todos', { filter }]  // Filtered

✅ Configure staleTime appropriately

tsx
staleTime: 1000 * 60 * 5 // 5 min - prevents excessive refetches

✅ Use isPending for initial loading state

tsx
if (isPending) return <Loading />
// isPending = no data yet AND fetching

✅ Throw errors in queryFn

tsx
if (!response.ok) throw new Error('Failed')

✅ Invalidate queries after mutations

tsx
onSuccess: () => {
  queryClient.invalidateQueries({ queryKey: ['todos'] })
}

✅ Use queryOptions factory for reusable patterns

tsx
const opts = queryOptions({ queryKey, queryFn })
useQuery(opts)
useSuspenseQuery(opts)
prefetchQuery(opts)

✅ Use gcTime (not cacheTime)

tsx
gcTime: 1000 * 60 * 60 // 1 hour

✅ Know your status flags

tsx
isPending      // no data yet, fetch in flight
isFetching     // any fetch in flight (including refetch)
isRefetching   // refetch specifically (data already cached)
isLoadingError // initial load failed
isPaused       // networkMode paused (e.g., offline)
isFetchingNextPage // useInfiniteQuery loading more
Never Do

❌ Never use v4 array/function syntax

tsx
// v4 (removed in v5):
useQuery(['todos'], fetchTodos, options) // ❌

// v5 (correct):
useQuery({ queryKey: ['todos'], queryFn: fetchTodos }) // ✅

❌ Never use query callbacks (onSuccess, onError, onSettled in queries)

tsx
// v5 removed these from queries:
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  onSuccess: (data) => {}, // ❌ Removed in v5
})

// Use useEffect instead:
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
useEffect(() => {
  if (data) {
    // Do something
  }
}, [data])

// Or use mutation callbacks (still supported):
useMutation({
  mutationFn: addTodo,
  onSuccess: () => {}, // ✅ Still works for mutations
})

❌ Never use deprecated options

tsx
// Deprecated in v5:
cacheTime: 1000 // ❌ Use gcTime instead
isLoading: true // ❌ Meaning changed, use isPending
keepPreviousData: true // ❌ Use placeholderData instead
onSuccess: () => {} // ❌ Removed from queries
useErrorBoundary: true // ❌ Use throwOnError instead

❌ Never assume isLoading means "no data yet"

tsx
// v5 changed this:
isLoading = isPending && isFetching // ❌ Now means "pending AND fetching"
isPending = no data yet // ✅ Use this for initial load

❌ Never forget initialPageParam for infinite queries

tsx
// v5 requires this:
useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam }) => fetchProjects(pageParam),
  initialPageParam: 0, // ✅ Required in v5
  getNextPageParam: (lastPage) => lastPage.nextCursor,
})

❌ Never use enabled with useSuspenseQuery

tsx
// Not allowed:
useSuspenseQuery({
  queryKey: ['todo', id],
  queryFn: () => fetchTodo(id),
  enabled: !!id, // ❌ Not available with suspense
})

// Use conditional rendering instead:
{id && <TodoComponent id={id} />}

Error Prevention

This skill prevents 8+ documented v5 migration issues. The most critical errors include:

  • Object syntax required (v4 function overloads removed)
  • Query callbacks removed (onSuccess/onError/onSettled)
  • isPending vs isLoading status changes
  • cacheTime renamed to gcTime
  • initialPageParam required for infinite queries
  • keepPreviousData replaced with placeholderData

For complete error catalog with before/after examples: Load references/top-errors.md when encountering errors or debugging v5 migration issues.


Project Configuration

Essential configuration files: package.json, tsconfig.json, .eslintrc.cjs

Key requirements:

  • React 18.3.1+ (uses useSyncExternalStore)
  • TypeScript strict mode recommended
  • ESLint plugin catches v4→v5 migration errors

For complete configuration templates: Load references/configuration-files.md when setting up new projects or troubleshooting build/type errors.


Common Patterns

Pattern 1: Dependent Queries
tsx
// Fetch user, then fetch user's posts
function UserPosts({ userId }: { userId: number }) {
  const { data: user } = useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
  })

  const { data: posts } = useQuery({
    queryKey: ['users', userId, 'posts'],
    queryFn: () => fetchUserPosts(userId),
    enabled: !!user, // Only fetch posts after user is loaded
  })

  if (!user) return <div>Loading user...</div>
  if (!posts) return <div>Loading posts...</div>

  return (
    <div>
      <h1>{user.name}</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

When to use: Query B depends on data from Query A

Pattern 2: Parallel Queries with useQueries
tsx
// Fetch multiple todos in parallel
function TodoDetails({ ids }: { ids: number[] }) {
  const results = useQueries({
    queries: ids.map(id => ({
      queryKey: ['todos', id],
      queryFn: () => fetchTodo(id),
    })),
  })

  const isLoading = results.some(result => result.isPending)
  const isError = results.some(result => result.isError)

  if (isLoading) return <div>Loading...</div>
  if (isError) return <div>Error loading todos</div>

  return (
    <ul>
      {results.map((result, i) => (
        <li key={ids[i]}>{result.data?.title}</li>
      ))}
    </ul>
  )
}

When to use: Fetch multiple independent queries in parallel

Pattern 3: Prefetching
tsx
import { useQueryClient } from '@tanstack/react-query'
import { todosQueryOptions } from './hooks/useTodos'

function TodoListWithPrefetch() {
  const queryClient = useQueryClient()
  const { data: todos } = useTodos()

  const prefetchTodo = (id: number) => {
    queryClient.prefetchQuery({
      queryKey: ['todos', id],
      queryFn: () => fetchTodo(id),
      staleTime: 1000 * 60 * 5, // 5 minutes
    })
  }

  return (
    <ul>
      {todos?.map(todo => (
        <li
          key={todo.id}
          onMouseEnter={() => prefetchTodo(todo.id)}
        >
          <Link to={`/todos/${todo.id}`}>{todo.title}</Link>
        </li>
      ))}
    </ul>
  )
}

When to use: Preload data before user navigates (on hover, on mount)

Pattern 4: Infinite Scroll
tsx
import { useInfiniteQuery } from '@tanstack/react-query'
import { useEffect, useRef } from 'react'

type Page = {
  data: Todo[]
  nextCursor: number | null
}

async function fetchTodosPage({ pageParam }: { pageParam: number }): Promise<Page> {
  const response = await fetch(`/api/todos?cursor=${pageParam}&limit=20`)
  return response.json()
}

function InfiniteTodoList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['todos', 'infinite'],
    queryFn: fetchTodosPage,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  })

  const loadMoreRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage) {
          fetchNextPage()
        }
      },
      { threshold: 0.1 }
    )

    if (loadMoreRef.current) {
      observer.observe(loadMoreRef.current)
    }

    return () => observer.disconnect()
  }, [fetchNextPage, hasNextPage])

  return (
    <div>
      {data?.pages.map((page, i) => (
        <div key={i}>
          {page.data.map(todo => (
            <div key={todo.id}>{todo.title}</div>
          ))}
        </div>
      ))}

      <div ref={loadMoreRef}>
        {isFetchingNextPage && <div>Loading more...</div>}
      </div>
    </div>
  )
}

When to use: Paginated lists with infinite scroll

Pattern 5: Query Cancellation
tsx
function SearchTodos() {
  const [search, setSearch] = useState('')

  const { data } = useQuery({
    queryKey: ['todos', 'search', search],
    queryFn: async ({ signal }) => {
      const response = await fetch(`/api/todos?q=${search}`, { signal })
      return response.json()
    },
    enabled: search.length > 2, // Only search if 3+ characters
  })

  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search todos..."
      />
      {data && (
        <ul>
          {data.map(todo => (
            <li key={todo.id}>{todo.title}</li>
          ))}
        </ul>
      )}
    </div>
  )
}

How it works:

  • When queryKey changes, previous query is automatically cancelled
  • Pass signal to fetch for proper cleanup
  • Browser aborts pending fetch requests
Pattern 6: Background Fetch Indicators
tsx
const { data, isFetching, isRefetching } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 1000 * 60 * 5,
})

return (
  <div>
    {isFetching && <Spinner label={isRefetching ? 'Refreshing…' : 'Loading…'} />}
    <TodoList data={data} />
  </div>
)

Why: isFetching stays true during background refetches so you can show a subtle "Refreshing" badge without losing cached data.


Using Bundled Resources

Templates (templates/)

Complete, copy-ready code examples:

  • package.json - Dependencies with exact versions
  • query-client-config.ts - QueryClient setup with best practices
  • provider-setup.tsx - App wrapper with QueryClientProvider
  • use-query-basic.tsx - Basic useQuery hook pattern
  • use-mutation-basic.tsx - Basic useMutation hook
  • use-mutation-optimistic.tsx - Optimistic update pattern
  • use-infinite-query.tsx - Infinite scroll pattern
  • custom-hooks-pattern.tsx - Reusable query hooks with queryOptions
  • error-boundary.tsx - Error boundary with query reset
  • devtools-setup.tsx - DevTools configuration

Example Usage:

bash
# Copy query client config
cp ~/.claude/skills/tanstack-query/templates/query-client-config.ts src/lib/

# Copy provider setup
cp ~/.claude/skills/tanstack-query/templates/provider-setup.tsx src/main.tsx

# Or run the bootstrap helper (installs deps + copies core files):
./scripts/example-script.sh . pnpm
References (references/)

Deep-dive documentation loaded when needed:

  • advanced-setup.md - Custom hooks, mutations, optimistic updates, DevTools, error boundaries
  • configuration-files.md - Complete package.json, tsconfig.json, .eslintrc.cjs templates
  • v4-to-v5-migration.md - Complete v4 → v5 migration guide
  • best-practices.md - Request waterfalls, caching strategies, performance
  • common-patterns.md - Reusable queries, optimistic updates, infinite scroll
  • official-guides-map.md - When to open each official doc and what it covers
  • typescript-patterns.md - Type safety, generics, type inference
  • testing.md - Testing with MSW, React Testing Library
  • top-errors.md - All 8+ errors with solutions
Show full SKILL.md (677 more words)Show less
Examples (examples/)
  • examples/README.md - Index of top 10 scenarios with official links
  • basic.tsx - Minimal list query
  • basic-graphql-request.tsx - GraphQL client + select
  • optimistic-update.tsx - onMutate snapshot/rollback
  • pagination.tsx - paginated list with placeholderData
  • infinite-scroll.tsx - useInfiniteQuery + IntersectionObserver
  • prefetching.tsx - prefetch on hover before navigation
  • suspense.tsx - useSuspenseQuery + boundary
  • default-query-function.tsx - global fetcher using queryKey
  • nextjs-app-router.tsx - App Router prefetch + hydrate (networkMode: 'always')
  • react-native.tsx - offline-first with AsyncStorage persister

When Claude should load these:

  • advanced-setup.md - When implementing custom query hooks, mutations, or error boundaries
  • configuration-files.md - When setting up new projects or troubleshooting build/type errors
  • v4-to-v5-migration.md - When migrating existing React Query v4 project
  • best-practices.md - When optimizing performance or avoiding waterfalls
  • common-patterns.md - When implementing specific features (infinite scroll, etc.)
  • typescript-patterns.md - When dealing with TypeScript errors or type inference
  • testing.md - When writing tests for components using TanStack Query
  • top-errors.md - When encountering errors not covered in main SKILL.md

Advanced Topics

Data Transformations with select
tsx
// Only subscribe to specific slice of data
function TodoCount() {
  const { data: count } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select: (data) => data.length, // Only re-render when count changes
  })

  return <div>Total todos: {count}</div>
}

// Transform data shape
function CompletedTodoTitles() {
  const { data: titles } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select: (data) =>
      data
        .filter(todo => todo.completed)
        .map(todo => todo.title),
  })

  return (
    <ul>
      {titles?.map((title, i) => (
        <li key={i}>{title}</li>
      ))}
    </ul>
  )
}

Benefits:

  • Component only re-renders when selected data changes
  • Reduces memory usage (less data stored in component state)
  • Keeps query cache unchanged (other components get full data)
Request Waterfalls (Anti-Pattern)
tsx
// ❌ BAD: Sequential waterfalls
function BadUserProfile({ userId }: { userId: number }) {
  const { data: user } = useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
  })

  const { data: posts } = useQuery({
    queryKey: ['posts', user?.id],
    queryFn: () => fetchPosts(user!.id),
    enabled: !!user,
  })

  const { data: comments } = useQuery({
    queryKey: ['comments', posts?.[0]?.id],
    queryFn: () => fetchComments(posts![0].id),
    enabled: !!posts && posts.length > 0,
  })

  // Each query waits for previous one = slow!
}

// ✅ GOOD: Fetch in parallel when possible
function GoodUserProfile({ userId }: { userId: number }) {
  const { data: user } = useQuery({
    queryKey: ['users', userId],
    queryFn: () => fetchUser(userId),
  })

  // Fetch posts AND comments in parallel
  const { data: posts } = useQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchPosts(userId), // Don't wait for user
  })

  const { data: comments } = useQuery({
    queryKey: ['comments', userId],
    queryFn: () => fetchUserComments(userId), // Don't wait for posts
  })

  // All 3 queries run in parallel = fast!
}
Server State vs Client State
tsx
// ❌ Don't use TanStack Query for client-only state
const { data: isModalOpen, setData: setIsModalOpen } = useMutation(...)

// ✅ Use useState for client state
const [isModalOpen, setIsModalOpen] = useState(false)

// ✅ Use TanStack Query for server state
const { data: todos } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})

Rule of thumb:

  • Server state: Use TanStack Query (data from API)
  • Client state: Use useState/useReducer (local UI state)
  • Global client state: Use Zustand/Context (theme, auth token)

Platform & Integration Notes

  • React Native: Works the same as web. Use @tanstack/query-async-storage-persister to persist cache to AsyncStorage; avoid window-focus refetch logic. DevTools panel not available natively—use Flipper or expose logs.
  • 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/nodes to flat shapes.
  • SSR / Next.js / TanStack Start: Use dehydrate/HydrationBoundary on the server and QueryClientProvider on the client. Set networkMode: 'always' for server prefetches so requests are never paused.
  • Suspense: Prefer useSuspenseQuery for routes already using Suspense. Do not combine with enabled; gate rendering instead.
  • Testing: Use @testing-library/react + @tanstack/react-query/testing helpers and mock network with MSW. Reset QueryClient between tests to avoid cache bleed.

Dependencies

Required:

  • @tanstack/react-query@5.101.4 - Core library
  • react@18.0.0+ - Uses useSyncExternalStore hook
  • react-dom@18.0.0+ - React DOM renderer

Recommended:

  • @tanstack/react-query-devtools@5.101.4 - Visual debugger (dev only)
  • @tanstack/eslint-plugin-query@5.101.4 - ESLint rules for best practices
  • typescript@5.2.0+ - For type safety and inference

Optional:

  • @tanstack/query-sync-storage-persister - Persist cache to localStorage
  • @tanstack/query-async-storage-persister - Persist to AsyncStorage (React Native)

Official Documentation


Package Versions (Verified 2026-08-03)

json
{
  "dependencies": {
    "@tanstack/react-query": "^5.101.4"
  },
  "devDependencies": {
    "@tanstack/react-query-devtools": "^5.101.4",
    "@tanstack/eslint-plugin-query": "^5.101.4"
  }
}

Verification:

  • npm view @tanstack/react-query version → 5.101.4
  • npm view @tanstack/react-query-devtools version → 5.101.4
  • npm view @tanstack/eslint-plugin-query version → 5.101.4
  • Last checked: 2026-08-03

Production Example

This skill is based on production patterns used in:

  • Build Time: ~6 hours research + development
  • Errors Prevented: 8 (all documented v5 migration issues)
  • Token Efficiency: ~65% savings vs manual setup
  • Validation: ✅ All patterns tested with TypeScript strict mode

Troubleshooting

Problem: "useQuery is not a function" or type errors

Solution: Ensure you're using v5 object syntax:

tsx
// ✅ Correct:
useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

// ❌ Wrong (v4 syntax):
useQuery(['todos'], fetchTodos)
Problem: Callbacks (onSuccess, onError) not working on queries

Solution: Removed in v5. Use useEffect or move to mutations:

tsx
// ✅ For queries:
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
useEffect(() => {
  if (data) {
    // Handle success
  }
}, [data])

// ✅ For mutations (still work):
useMutation({
  mutationFn: addTodo,
  onSuccess: () => { /* ... */ },
})
Problem: isLoading always false even during initial load

Solution: Use isPending instead:

tsx
const { isPending, isLoading, isFetching } = useQuery(...)
// isPending = no data yet
// isLoading = isPending && isFetching
// isFetching = any fetch in progress
Problem: cacheTime option not recognized

Solution: Renamed to gcTime in v5:

tsx
gcTime: 1000 * 60 * 60 // 1 hour
Problem: useSuspenseQuery with enabled option gives type error

Solution: enabled not available with suspense. Use conditional rendering:

tsx
{id && <TodoComponent id={id} />}
Problem: Data not refetching after mutation

Solution: Invalidate queries in onSuccess:

tsx
onSuccess: () => {
  queryClient.invalidateQueries({ queryKey: ['todos'] })
}
Problem: Infinite query requires initialPageParam

Solution: Always provide initialPageParam in v5:

tsx
useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam }) => fetchProjects(pageParam),
  initialPageParam: 0, // Required
  getNextPageParam: (lastPage) => lastPage.nextCursor,
})
Problem: keepPreviousData not working

Solution: Replaced with placeholderData:

tsx
import { keepPreviousData } from '@tanstack/react-query'

useQuery({
  queryKey: ['todos', page],
  queryFn: () => fetchTodos(page),
  placeholderData: keepPreviousData,
})

Complete Setup Checklist

Use this checklist to verify your setup:

  • Installed @tanstack/react-query@5.101.4+
  • Installed @tanstack/react-query-devtools (dev dependency)
  • Created QueryClient with configured defaults
  • Wrapped app with QueryClientProvider
  • Added ReactQueryDevtools component
  • Created first query using object syntax
  • Tested isPending and error states
  • Created first mutation with onSuccess handler
  • Set up query invalidation after mutations
  • Configured staleTime and gcTime appropriately
  • Using array queryKey consistently
  • Throwing errors in queryFn
  • No v4 syntax (function overloads)
  • No query callbacks (onSuccess, onError on queries)
  • Using isPending (not isLoading) for initial load
  • DevTools working in development
  • TypeScript types working correctly
  • Production build succeeds

Questions? Issues?

  1. Check references/top-errors.md for complete error solutions
  2. Verify all steps in the setup process
  3. Check official docs: https://tanstack.com/query/latest
  4. Ensure using v5 syntax (object syntax, gcTime, isPending)
  5. Join Discord: https://tlinz.com/discord

© secondsky, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 35 other files (scripts, references, assets) in plugins/tanstack-query/skills/tanstack-query of secondsky/claude-skills.

  • SKILL.md
  • assets/example-template.txt
  • examples/README.md
  • examples/basic-graphql-request.tsx
  • examples/basic.tsx
  • examples/default-query-function.tsx
  • examples/infinite-scroll.tsx
  • examples/nextjs-app-router.tsx
  • examples/optimistic-update.tsx
  • examples/pagination.tsx
  • examples/prefetching.tsx
  • examples/react-native.tsx
  • examples/suspense.tsx
  • references/advanced-setup.md
  • references/best-practices.md
  • references/common-patterns.md
  • references/configuration-files.md
  • references/example-reference.md
  • … and 18 more

Open the folder on GitHubat commit 8837836

Compare with similar skills

Tanstack Query next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Tanstack Query compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Tanstack Query this skillsecondsky/claude-skills227—~7.9kAutomated safety check: PassMIT
Tanstack QueryMindrally/skills267—~3.6kAutomated safety check: PassApache-2.0
Olore Tanstack Query 3olorehq/olore103—~700Automated safety check: PassMIT
Olore Tanstack Query Latestolorehq/olore103—~487Automated safety check: PassMIT
Tanstack Query Best PracticesDeckardGer/tanstack-agent-skills2221 repos~1.2kAutomated safety check: PassMIT
Olore Tanstack Query 4olorehq/olore103—~583Automated safety check: PassMIT

Similar skills

  • Tanstack Query

    Mindrally/skills

    Best practices for TanStack Query v5 (React Query) server state management, including query key factories, the queryOptions helper, mutations, optimistic updates, infinite queries, and Suspense mode.

    267 GitHub stars~3.6k tokensUpdated 1 mo ago
    Frontend & DesignAuto-check passed
  • Local TanStack Query v3 (React Query) documentation reference.

    103 GitHub stars~700 tokensUpdated today
    Frontend & DesignAuto-check passed
  • Local TanStack Query documentation reference (latest). An agent skill from olorehq/olore.

    103 GitHub stars~487 tokensUpdated today
    Frontend & DesignAuto-check passed
  • Tanstack Query Best Practices

    DeckardGer/tanstack-agent-skills

    TanStack Query (React Query) best practices for data fetching, caching, mutations, and server state management.

    222 GitHub starsUsed in 1 repo~1.2k tokens
    Backend & APIsAuto-check passed
  • Local TanStack Query documentation reference (v4). An agent skill from olorehq/olore.

    103 GitHub stars~583 tokensUpdated today
    Backend & APIsAuto-check passed
  • Tanstack Query

    bskimball/tanstack-hono

    Powerful asynchronous state management, server-state utilities, and data fetching for TS/JS, React, Vue, Solid, Svelte & Angular.

    119 GitHub starsUsed in 1 repo~5.4k tokens
    Frontend & DesignAuto-check passed

More from secondsky/claude-skills

All 169 skills in this repo
  • Tanstack AI

    secondsky/claude-skills

    TanStack AI (alpha) provider-agnostic type-safe chat with streaming for OpenAI, Anthropic, Gemini, Ollama.

    227 GitHub starsUsed in 1 repo~3.6k tokens
    Auto-check: notes
  • Auto Animate

    secondsky/claude-skills

    AutoAnimate (@formkit/auto-animate) zero-config animations for React.

    227 GitHub stars~2.9k tokensUpdated 9 days ago
    Auto-check passed
  • Base UI React

    secondsky/claude-skills

    MUI Base UI unstyled React components with Floating UI. An agent skill from secondsky/claude-skills.

    227 GitHub stars~1.9k tokensUpdated 9 days ago
    Auto-check passed
  • Cloudflare Images

    secondsky/claude-skills

    This skill should be used when the user asks to "upload images to Cloudflare", "implement direct creator upload", "configure image transformations", "optimize WebP/AVIF", "create image variants"…

    227 GitHub stars~3.6k tokensUpdated 9 days ago
    Auto-check: notes
  • Cloudflare Nextjs

    secondsky/claude-skills

    Deploy Next.js to Cloudflare Workers via the OpenNext adapter (@opennextjs/cloudflare).

    227 GitHub stars~5.3k tokensUpdated 9 days ago
    Auto-check: notes
  • Cloudflare Sandbox

    secondsky/claude-skills

    Cloudflare Sandboxes SDK for secure code execution in Linux containers at edge.

    227 GitHub stars~4.5k tokensUpdated 9 days ago
    Auto-check passed

Works with

Questions about Tanstack Query

What does Tanstack Query do?

TanStack Query v5 (React Query) server state management. An agent skill from secondsky/claude-skills. Tanstack Query is an agent skill from secondsky/claude-skills. TanStack Query v5 (React Query) server state management.

When should I use Tanstack Query?

Tanstack Query fits situations like: encountering v4 migration; invalidation errors.

How do I install Tanstack Query in Claude Code?

Run `npx skills add secondsky/claude-skills --skill tanstack-query -a claude-code`. Or copy the skill folder (plugins/tanstack-query/skills/tanstack-query in secondsky/claude-skills) into .claude/skills/tanstack-query in your project. Claude Code loads it when a task matches its description.

How do I install Tanstack Query in Codex?

Run `npx skills add secondsky/claude-skills --skill tanstack-query -a codex`. Or copy the skill folder (plugins/tanstack-query/skills/tanstack-query in secondsky/claude-skills) into .agents/skills/tanstack-query in your project. Codex loads it when a task matches its description.

Can I use Tanstack Query in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add secondsky/claude-skills --skill tanstack-query -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/tanstack-query, .gemini/skills/tanstack-query, .github/skills/tanstack-query and .opencode/skills/tanstack-query in your project.

What does Tanstack Query need to run?

Going by SKILL.md and its folder, Tanstack Query needs the command-line tools its instructions call (pnpm, npm and bun). Our summary lists: Node.js.

Does Tanstack Query access the network?

SKILL.md names 3 domains. As links in the text: tanstack.com, tlinz.com and github.com. This is read from the text; nothing was executed.

Is Tanstack Query safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Tanstack Query use?

Tanstack Query is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Tanstack Query use?

About 7.9k tokens (SKILL.md is roughly 32k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 15k tokens, read only when the agent opens those files.

What are the alternatives to Tanstack Query?

Skills that share tags, products or a category with Tanstack Query: Tanstack Query (Mindrally/skills, 267 stars), Olore Tanstack Query 3 (olorehq/olore, 103 stars), Olore Tanstack Query Latest (olorehq/olore, 103 stars) and Tanstack Query Best Practices (DeckardGer/tanstack-agent-skills, 222 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Tanstack Query?

secondsky (a GitHub user) maintains it in secondsky/claude-skills, which has 227 GitHub stars. The repository holds 169 skills in this directory. The repository was last updated on September 28, 2026.

Source: secondsky/claude-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.