tanstack-query
Skill4 starsChanged 20 days agoMITView on GitHub
TanStack Query v5 (React Query) async state management for React. Use when implementing or reviewing code that uses `@tanstack/react-query` — query keys, queryOptions, useQuery/useSuspenseQuery, mutations and invalidation, QueryClient defaults, SSR hydration, or infinite queries.
19 other projects use a version of this.
Adopted by
Most recently taken up by
- yuan-alex/cedartheir own version, first seen Nov 8, 2026
- thongdn-it/react-agent-skillstheir own version, first seen Nov 1, 2026
- kriscard/Skillstheir own version, first seen Nov 1, 2026
- louisbrulenaudet/monorepo-templatetheir own version, first seen Oct 29, 2026
- LaoitdevOpen/laoitdev-skillstheir own version, first seen Oct 20, 2026
- davidanthonyn/claude-skillstheir own version, first seen Oct 20, 2026
Used with
Projects with it also use tanstack-form, tanstack-table and 1 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
heryTzSkilltanstack-form
Create or modify forms using TanStack Form's composition API. Uses formOptions() as the form shape primitive, withForm HOC for decomposing large forms into sections, withFieldGroup for reusable field groups across forms, Zod for validation, and i18n-prefixed error messages. Use when creating or modifying forms in the web app.
Also in 3 projects that use this
JasonPaffSkilltanstack-table
Build headless data tables with TanStack Table v8. Server-side pagination, filtering, sorting, and virtualization for Cloudflare Workers + D1. Prevents 12 documented errors.
Also in 3 projects that use this
SentrySkillreview
Fixture review skill.
Also in 3 projects that use this
Other versions19 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 19
- Kiranism/next-shadcn-dashboard-starter★ 7.2ksame name
- modrinth/code★ 2.5ksame name
- operately/operately★ 618same name
- Mindrally/skills★ 275same name
- TangleML/tangle-ui★ 104same name
- PyModel/react-frontend-skills★ 23same name
- louisbrulenaudet/monorepo-template★ 19same name
- kriscard/Skills★ 15same name
- thongdn-it/react-agent-skills★ 5same name
- dvaJi/argos★ 3same name
9 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository39 other files it gives its agents.
- AGENTS.mdAGENTS.md
- authoringSkill
- chrome-devtoolsSkill
- gamificationSkill
- imagegenSkill
- improve-architectureSkill
- instructionsSkill
- monorepoSkill
- reverse-engineerSkill
- runbookSkill
- security-reviewSkill
- setup-harnessSkill
- skillsSkill
- unslopSkill
- video-reportSkill
- youtube-transcriptSkill
- code-reviewSkill
- debugSkill
- interviewSkill
- prototypeSkill
- researchSkill
- workSkill
- adsenseSkill
- astryxSkill
- cloudflare-cliSkill
- dokploy-best-practicesSkill
- extract-themeSkill
- nextjsSkill
- payload-cmsSkill
- shadersSkill
- swrSkill
- tanstack-routerSkill
- tanstack-startSkill
- tanstack-storeSkill
- tauriSkill
- vite-plusSkill
- web-assets-optimizationSkill
- web-extensionSkill
- zodSkill
The file
We split this skill into the rules it gives an agent
5 rules. 0 are common, 3 are distinctive, and 2 are very specific to this project.
0
Common
Most of the 18 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 44
If the app server-renders, identify the prefetch boundary before writing any hook — see ssr.md.
- Very specific: line 161
Never retry blindly on 4xx: a retry predicate that inspects the status avoids three doomed round-trips on every 404.
- Very specific: line 188
Call hooks unconditionally.
- Distinctive: line 257
Put the page in the key and keep the previous page on screen while the next one loads:
- Distinctive: line 279
Every query key is an array that contains all variables shaping the response, ordered generic-to-specific so prefix invalidation works.
“Common” is measured: kept by at least 6 in 10 of the 18 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TanStack Query
Reference for TanStack Query v5 (@tanstack/react-query), the async state
manager for server data in React. Prefer the project's existing API client,
query-key conventions, and hook layer; apply these rules on top so cache
identity, staleness, and invalidation stay predictable.
TanStack Query owns server state: data it did not create, stored elsewhere, potentially stale at any moment. It is not a replacement for client state (form inputs, modal toggles, wizard steps) — keep those in React state.
Branch-specific references, loaded on demand:
- ssr.md — prefetch,
dehydrate,HydrationBoundary, Next.js App Router and Server Components. - mutations.md —
useMutationlifecycle, optimistic updates, rollback, invalidation strategy. - infinite-queries.md —
useInfiniteQuery, cursors, bi-directional lists, page-limited caches. - typescript.md — inference, narrowing, global
Registertypes,skipToken.
First Checks
- Confirm
@tanstack/react-queryis installed and on v5 (package.json, lockfile). v4 code differs on renamed options —cacheTimebecamegcTime,useErrorBoundarybecamethrowOnError,keepPreviousDatabecameplaceholderData: keepPreviousData. - Find where the
QueryClientis created and whichdefaultOptionsare set —staleTime,gcTime,retrychange the behavior of every hook below. - Find the existing query-key convention (flat arrays, key factories, or
queryOptionsfunctions) and reuse it instead of inventing a new one. - Find the existing fetch layer: query functions must throw on failure, and
fetchdoes not throw on a 4xx/5xx by itself. - If the app server-renders, identify the prefetch boundary before writing any hook — see ssr.md.
Keys: The Key Is The Cache Identity
A query key is an array, serializable with JSON.stringify, and unique to
the data it describes. Every variable the query function reads and that
changes the response belongs in the key. Keys act as dependencies: when the
key changes, the query is a different cache entry and refetches.
| Case | Pattern | Why |
| Generic list | queryKey: ['todos'] | Constant key for a non-hierarchical resource. |
| Item by id | queryKey: ['todo', 5] | Primitive identifies the item. |
| Extra parameters | queryKey: ['todos', { status, page }] | Object holds the parameters that shape the response. |
| Scope | queryKey: ['todos', tenantId, { status }] | Scope-changing inputs are part of the identity. |
Hashing rules that decide whether two keys are the same entry:
- Object keys are hashed deterministically:
{ status, page }and{ page, status }are the same key, and a property set toundefinedis ignored. - Array item order matters:
['todos', status, page]and['todos', page, status]are two different entries. - Prefix matching drives invalidation:
['todos']matches['todos', { page: 1 }]unlessexact: trueis passed. Order keys from generic to specific so the prefix you want to invalidate is the leftmost segment.
Colocate With queryOptions
queryOptions returns its input unchanged at runtime, but it ties queryKey
and queryFn together and carries the result type into every consumer. Prefer
it over loose key constants as soon as a query is used in more than one place.
import { queryOptions } from '@tanstack/react-query'
export function todoOptions(todoId: string) {
return queryOptions({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
staleTime: 5 * 60 * 1000,
})
}
useQuery(todoOptions('5'))
useSuspenseQuery(todoOptions('5'))
queryClient.prefetchQuery(todoOptions('5'))
queryClient.setQueryData(todoOptions('5').queryKey, nextTodo)useQueries({ queries: [todoOptions('1'), todoOptions('2')] })runs them in parallel from the same definition.- Override per component with a spread:
useQuery({ ...todoOptions(id), select }). infiniteQueryOptionsis the equivalent helper for infinite queries;mutationOptionsfor mutations.queryClient.getQueryData(todoOptions(id).queryKey)is typed thanks to the helper — without it the result isunknown(typescript.md).
Query Functions
A query function returns a promise that resolves the data or throws.
useQuery({
queryKey: ['todos', todoId],
queryFn: async ({ signal }) => {
const response = await fetch(`/api/todos/${todoId}`, { signal })
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
})- Resolving
undefinedis treated as a failed query. Resolvenullto store "nothing" as a success. fetchdoes not throw on HTTP error statuses — throw explicitly.- The function receives a
QueryFunctionContext:queryKey,client,signal(pass it through for cancellation), andmeta.
Defaults That Decide Behavior
v5 defaults, worth knowing because they explain most surprises:
| Option | Default | Effect |
staleTime | 0 | Cached data is stale immediately, so it refetches on mount, window focus, and reconnect. |
gcTime | 5 * 60 * 1000 | Inactive entries (no mounted observer) are garbage collected after 5 minutes. |
retry | 3 | Failures retry three times with exponential backoff before surfacing an error. |
structuralSharing | true | Unchanged parts of the response keep their reference, so consumers do not re-render. |
mutation retry | 0 | Mutations do not retry by default. |
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
retry: (failureCount, error) =>
error instanceof HttpError && error.status < 500 ? false : failureCount < 3,
},
},
})staleTimeis the main lever against excessive refetching; tune it per endpoint (viaqueryOptions) rather than disablingrefetchOnWindowFocusglobally.staleTime: Infinitystops staleness-based refetching but still responds toinvalidateQueries.staleTime: 'static'also ignores manual invalidation andrefetchOn*: 'always'— reserve it for data that cannot change while the app runs (boot-time feature flags, permissions loaded at login).gcTimeis a cache-retention timer, not a freshness timer; it only starts once a query has no observers.- Never retry blindly on 4xx: a
retrypredicate that inspects the status avoids three doomed round-trips on every 404.
Reading A Query
const { data, error, status, isPending, isFetching, isError } = useQuery(
todoOptions(todoId),
)status: 'pending' | 'error' | 'success'describes the data;fetchStatus: 'fetching' | 'paused' | 'idle'describes the request. A query with cached data that is refetching issuccess+fetching.isPendingmeans "no data yet". UseisFetchingfor background-refresh indicators over already-rendered data.- For disabled or lazy queries use
isLoading(isPending && isFetching) — a disabled query ispendingforever and would pin a spinner on screen. selectsubscribes the component to a slice of the data and re-runs only whendataor the function reference changes; extract it to a stable reference or wrap it inuseCallbackinstead of inlining.- Re-renders are tracked per accessed property via a Proxy. Object rest
destructuring (
const { data, ...rest }) touches every property and disables that optimization.
Disabling And Dependent Queries
Call hooks unconditionally; put the condition in enabled or in the query
function.
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: () => getUserByEmail(email),
})
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: () => getProjectsByUser(user!.id),
enabled: !!user?.id,
})- A disabled query ignores
invalidateQueriesandrefetchQueries, does not fetch on mount, and does not refetch in the background. skipTokenas thequeryFnis the type-safe alternative toenabled: falseand removes the non-null assertion — butrefetch()then fails withMissing queryFn. Useenabled: falsewhen manualrefetch()is required.- Dependent queries are a client-side waterfall. When both are needed for the first paint, prefetch them on the server (ssr.md).
Suspense
const { data } = useSuspenseQuery(todoOptions(todoId))datais guaranteed defined, so nostatushandling is needed; loading goes to<Suspense>and errors to an error boundary.- The trade-off: no
enabled, noplaceholderData, and queries inside one component fetch in serial. UseuseSuspenseQueriesto parallelize. - Errors are only thrown to the boundary when there is no data to show
(
throwOnErrordefaults to(error, query) => typeof query.state.data === 'undefined'). Throw manually if every error must reach the boundary. - Reset errors on retry with
QueryErrorResetBoundaryoruseQueryErrorResetBoundary, wired to the error boundary'sonReset. - Wrap key changes in
startTransitionso the fallback does not replace the rendered UI on every update. - With SSR, only use
useSuspenseQueryfor queries that are always prefetched — a forgotten prefetch produces a hydration mismatch.
Mutations And Invalidation
const queryClient = useQueryClient()
const { mutate, isPending } = useMutation({
mutationFn: addTodo,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})invalidateQueriesmarks matching queries stale — overriding theirstaleTime— and refetches the ones currently rendered.- Match by prefix by default; add
exact: trueto hit a single entry, orpredicatefor anything finer. - Return the promise from
onSuccess/onSettledto keep the mutationisPendinguntil the refetch lands. mutateis fire-and-forget with callbacks;mutateAsyncreturns a promise you must catch yourself.
Optimistic updates, rollback, useMutationState, and offline behavior are in
mutations.md.
Pagination
Put the page in the key and keep the previous page on screen while the next one loads:
import { keepPreviousData, useQuery } from '@tanstack/react-query'
const { data, isPlaceholderData } = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
placeholderData: keepPreviousData,
})Without it the UI flips between pending and success on every page change.
Guard the "next" control with isPlaceholderData so a page is not skipped.
placeholderData is never written to the cache; initialData is.
For "load more" and infinite scroll, see infinite-queries.md.
Review Checklist
- Every query key is an array that contains all variables shaping the response, ordered generic-to-specific so prefix invalidation works.
- Shared queries go through a
queryOptionsfunction rather than duplicated key/fn pairs. - Query functions throw on HTTP errors and never resolve
undefined. staleTimeis set deliberately per endpoint;retrydoes not retry client-error responses.- Loading UI uses
isPending/isLoadingcorrectly and distinguishes background refetches viaisFetching. - Conditional fetching uses
enabledorskipToken, never a conditional hook call. - Suspense queries are always prefetched on server-rendered routes, and error boundaries can be reset.
- Mutations invalidate or update every affected key, and optimistic updates cancel in-flight queries, snapshot, and roll back (mutations.md).
- Server rendering creates one
QueryClientper request and hydrates throughHydrationBoundary(ssr.md). - Infinite queries define
initialPageParamand returnundefinedfromgetNextPageParamat the end of the list (infinite-queries.md).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

