TanStack Query
Overview
TanStack Query is an async state manager, not a data fetching library. You provide a queryFn that returns a Promise; React Query handles caching, deduplication, background updates, and stale data management.
When to use: Infinite scrolling, offline-first apps, auto-refetching on focus/reconnect, complex cache invalidation, React Native, hybrid server/client apps.
When NOT to use: Purely synchronous state (useState/Zustand), normalized GraphQL caching (Apollo/urql), server-components-only apps (native fetch), simple fetch-and-display (server components).
Quick Reference
| Pattern | API | Key Points |
|---|---|---|
| Basic query | useQuery({ queryKey, queryFn }) | Include params in queryKey |
| Suspense query | useSuspenseQuery(options) | No enabled option allowed |
| Parallel queries | useQueries({ queries, combine }) | Dynamic parallel fetching |
| Dependent query | useQuery({ enabled: !!dep }) | Wait for prerequisite data |
| Query options | queryOptions({ queryKey, queryFn }) | Reusable, type-safe config |
| Basic mutation | useMutation({ mutationFn, onSuccess }) | Invalidate on success |
| Mutation state | useMutationState({ filters, select }) | Cross-component mutation tracking |
| Optimistic update | onMutate -> cancel -> snapshot -> set | Rollback in onError |
| Infinite query | useInfiniteQuery({ initialPageParam }) | initialPageParam required in v5 |
| Prefetch | queryClient.prefetchQuery(options) | Preload on hover/intent |
| Invalidation | queryClient.invalidateQueries({ queryKey }) | Fuzzy-matches by default, active only |
| Cancellation | queryFn: ({ signal }) => fetch(url, { signal }) | Auto-cancel on key change |
| Select transform | select: (data) => data.filter(...) | Structural sharing preserved |
| Skip token | queryFn: id ? () => fetch(id) : skipToken | Type-safe conditional disabling |
| Serial mutations | useMutation({ scope: { id } }) | Same scope ID runs mutations in serial |
v5 Migration Quick Reference
| v4 (Removed) | v5 (Use Instead) |
|---|---|
useQuery(key, fn, opts) | useQuery({ queryKey, queryFn, ...opts }) |
cacheTime | gcTime |
isLoading (no data) | isPending |
keepPreviousData: true | placeholderData: keepPreviousData |
onSuccess/onError on queries | useEffect or mutation callbacks |
useErrorBoundary | throwOnError |
No initialPageParam | initialPageParam required |
Error type unknown | Error type defaults to Error |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Checking isPending before data | Data-first: check data -> error -> isPending |
| Copying server state to local useState | Use data directly or derived state pattern |
| Creating QueryClient in component | Create once outside component or in useState |
Using refetch() for parameter changes | Include params in queryKey, let it refetch automatically |
| Same key for useQuery and useInfiniteQuery | Use distinct key segments (different cache structures) |
| Inline select without memoization | Extract to stable function or useCallback |
Using catch without re-throwing | Throw errors in queryFn (fetch doesn't reject on 4xx/5xx) |
| Manual generics on useQuery | Type the queryFn return, let inference work |
| Destructuring query for type narrowing | Keep query object intact for proper narrowing |
Using enabled with useSuspenseQuery | Use conditional rendering to mount/unmount component |
| Not awaiting prefetch for SSR | Await prefetchQuery to avoid hydration mismatches |
invalidateQueries not refetching all | Use refetchType: 'all' for inactive queries |
Delegation
- Query pattern discovery: Use
Exploreagent - Cache strategy review: Use
Taskagent - Code review: Delegate to
code-revieweragent
If the
tanstack-routerskill is available, delegate route loader and preloading patterns to it. If thetanstack-formskill is available, delegate form submission and mutation coordination to it. If thetanstack-tableskill is available, delegate server-side table patterns to it. If thetanstack-startskill is available, delegate server functions and SSR data loading to it. If thetanstack-devtoolsskill is available, delegate query cache debugging and inspection to it. If thetanstack-dbskill is available, delegate reactive client-side database and live query patterns to it. If thetanstack-virtualskill is available, delegate list virtualization and infinite scroll rendering to it. If thetanstack-storeskill is available, delegate shared client-side reactive state management to it. If theelectricsqlskill is available, delegate ElectricSQL real-time Postgres sync patterns to it. If thelocal-firstskill is available, delegate local-first architecture decisions and sync engine selection to it.
References
- Basic patterns, architecture, and query variants
- Query keys and factory patterns
- Mutations, optimistic updates, and MutationCache
- Cache operations, staleTime vs gcTime, seeding
- Data transformations and select patterns
- Performance optimization with render tracking and structural sharing
- Error handling strategies
- Infinite queries and pagination
- Offline mode and persistence
- WebSocket and real-time integration
- SSR and hydration patterns
- TypeScript patterns
- Testing with MSW and React Testing Library
- Known v5 issues and workarounds
- Caching coordination with Router — single-source caching strategy, disabling Router cache, coordinated configuration