Cheatsheet

Cheatsheet — TanStack Query

Decision rules

  • Data is server-owned and async → TanStack Query. Client-only UI state (open/closed, form draft) → plain useState/context, not this library.
  • Reading data → useQuery. Writing data (create/update/delete/side-effect) → useMutation. Never fetch-with-side-effects inside useMutation's mutationFn as a substitute for a real query.
  • Mutation succeeded and you already have the fresh object → setQueryData, skip the refetch. Otherwise → invalidateQueries from onSuccess.
  • Optimistic UI needed in one place → read variables/isPending off the mutation. Needed in several places → cache-write in onMutate + rollback in onError.
  • A second query needs the first query's result → enabled: !!value. But check first whether a combined backend endpoint would remove the dependency entirely.
  • More than one useSuspenseQuery in a component → merge into one useSuspenseQueries call. Never leave them side by side — they'll serialize.
  • List/detail/tab UI whose key changes on interaction → placeholderData: keepPreviousData. Prevents the loading-flash between keys.
  • SSR/SSG page → prefetch in the loader, dehydrate, <HydrationBoundary>, non-zero staleTime. Never construct QueryClient at module scope in an SSR app.
  • Need the app to survive offline / reload without a cold cache → a persister. All queries worth persisting, simple case → persistQueryClient. Only some queries, or different maxAge per query → experimental_createQueryPersister.

Thresholds & defaults

SettingDefaultNotes
staleTime0Data is stale immediately unless set otherwise
gcTime5 min (client), Infinity (server)Independent of staleTime
Query retry3, exponential backoff (client); 0 (server)
Mutation retry0Opt-in, unlike queries
networkMode'online'Pauses instead of failing when offline
refetchOnWindowFocus/Mount/ReconnecttrueOnly fires if data is stale
notifyOnChangePropstracked (Proxy-based)'all' opts out
structuralSharingtrueJSON-compatible data only
Persisted cache maxAge24 hoursRaise gcTime to match, or GC discards it early

status vs. fetchStatus quick reference

statusfetchStatus
Answers"Do we have data?""Is the fetcher running right now?"
Valuespending / error / successfetching / paused / idle
Derived flagsisPending/isError/isSuccessisFetching/isPaused
Combined flagisLoading = isPending && isFetching; isRefetching = isFetching && !isPending

Migration renames at a glance (→ v5)

OldNew
cacheTimegcTime
useErrorBoundarythrowOnError
status: 'loading' / isLoadingstatus: 'pending' / isPending
keepPreviousData: trueplaceholderData: keepPreviousData
<Hydrate><HydrationBoundary>
fetchQuery/prefetchQueryqueryClient.query()

ESLint rules worth enabling first

exhaustive-deps (fixable) · stable-query-client (fixable) · no-rest-destructuring · no-void-query-fn — install flat/recommended and get all of these plus more.