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 thetanstack-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