Skip to content
masterclassBuddyTech Buzz5 fresh blogs · 5:00 AM IST

Technology handbook

TanStack Query

Queries, caching, invalidation, mutations, pagination, prefetching, SSR and testing for server state.

Topics
40
Sections
6

Hooks

useQueries

Run a dynamic list of queries together and read their results as an array.

  • The number of queries may change between renders, which a list of hooks cannot do.
  • Each entry keeps its own key, status and cache lifetime.
  • A combine function reduces the array into one value without an extra render.

useSuspenseQuery

Suspend the component until data is ready instead of branching on a loading flag.

  • Data is never undefined in the component, so the type has no loading branch.
  • The nearest Suspense boundary owns the fallback, not the component itself.
  • Two suspense queries in one component fetch in series unless they are hoisted.

useSuspenseInfiniteQuery

Page through data under Suspense, where the first page suspends and the rest do not.

  • The first page suspends; later pages resolve without unmounting the list.
  • Rendered rows stay on screen while the next page is in flight.
  • The boundary is for the initial load only, so scrolling never blanks the view.

useQueryClient

Reach the client from a component to invalidate, set or read cached data.

  • The client is how imperative code reaches the cache outside a hook result.
  • It reads context, so it works only under the provider that owns the cache.
  • Use it in an event handler; use hooks for anything that should re-render.

useIsFetching and useIsMutating

Drive a global activity indicator from counts rather than from each query's flags.

  • A count of active requests drives one top-level progress bar.
  • Filters narrow the count to a feature area rather than the whole application.
  • Background revalidation is included, so a spinner here means activity, not blocking.

useMutationState

Observe mutations started elsewhere, keyed by mutation key.

  • A list can show a pending row created by a form somewhere else entirely.
  • Mutations are matched by key, so unrelated writes stay out of the view.
  • It exposes variables and status without lifting the mutation into shared state.

usePrefetchQuery

Start a fetch during render for data a child will need immediately.

  • Starting the fetch during render removes a waterfall a child would create.
  • It returns nothing: the point is the cache entry, not a value to read.
  • Prefetch what a child certainly needs, not what it might need.

Query options factories

Share key, function and options through queryOptions so every hook agrees on them.

  • One factory keeps the key and the fetcher from drifting apart across files.
  • Prefetch, invalidate and read all take the same object, so they cannot disagree.
  • Types flow from the factory, so a changed response shape breaks every consumer.

Hook-level select

Transform or narrow data in the hook so the component re-renders only when its slice changes.

  • A selector narrows the data so unrelated fields cannot cause a render.
  • It runs after the fetch, on cached data, not as part of the request.
  • An inline function that returns a new object defeats the comparison it relies on.

Hooks in tests

Wrap the hook in a fresh QueryClientProvider per test and turn retries off.

  • A fresh client per test stops one case seeding another through the cache.
  • Retries off turns a deliberate failure into an immediate assertion.
  • Assert on rendered output rather than on the internals of the cache.

Query fundamentals

useQuery

Read server state declaratively and render from status, data and error rather than manual loading flags.

  • The hook owns fetching, caching, retries and revalidation for one key.
  • Several components asking for the same key share one request and one entry.
  • Rendering from status is what removes the effect-plus-state pattern entirely.

Query keys

Identify a cache entry with a serializable key whose parts describe exactly what was requested.

  • The key is the cache identity, so anything that changes the request belongs in it.
  • Keys are hashed deterministically, so property order in an object does not matter.
  • Prefix keys by feature so a whole area can be invalidated in one call.

Query functions

Return a promise that rejects on failure so the library can distinguish success from error.

  • A fetcher must throw on a failed response, because fetch does not reject on 404.
  • It receives the key and an abort signal as its argument.
  • Keep it free of component state so the same call can run from a prefetch.

QueryClient and provider

Create one client per application and share it so components read the same cache.

  • Defaults set on the client apply to every query that does not override them.
  • One client per application on the client; one per request on the server.
  • Creating the client inside a component body makes a new cache on every render.

Status and fetchStatus

Separate whether data exists from whether a request is currently in flight.

  • Status answers whether data exists; fetch status answers whether a request is running.
  • Cached data with a background refetch is success and fetching at the same time.
  • Showing a full-page spinner for a background refetch is the usual mistake.

staleTime and gcTime

Decide how long data is considered fresh, and how long an unused entry survives in memory.

  • Stale time controls refetching; garbage collection time controls memory.
  • A stale time of zero still serves from cache, it just revalidates immediately.
  • Data that changes rarely deserves minutes of freshness, not the default.

Caching & invalidation

invalidateQueries

Mark matching entries stale so active observers refetch instead of clearing the cache.

  • Invalidation refetches what is on screen and marks the rest stale for later.
  • Removing an entry instead throws away data a component is still rendering.
  • Awaiting it lets a handler wait until dependent views are up to date.

Query filters

Target a subset of the cache by key prefix, status or staleness when invalidating or cancelling.

  • A filter matches by key prefix, so one call can reach a whole feature.
  • Predicates handle the cases a prefix cannot express.
  • The same filters apply to cancelling, removing and reading the cache.

setQueryData

Write a known value into the cache directly to reflect a result you already have.

  • Writing the server response directly saves a round trip the client already made.
  • The updater must return a new value; mutating the cached object skips the update.
  • Data written this way is immediately stale unless the entry says otherwise.

Structural sharing

Keep unchanged references stable across refetches so consumers do not rerender needlessly.

  • Unchanged parts of a response keep their previous references after a refetch.
  • That keeps memoised children and effect dependencies from firing needlessly.
  • It only works on plain JSON-like data, not on class instances.

refetchOnWindowFocus

Choose when regaining focus should revalidate, and when that would surprise the reader.

  • Refetching on focus keeps a dashboard current after a tab sits idle.
  • The same behaviour on a half-filled form can replace what the user was reading.
  • Set it per query, because the right answer differs by screen.

Dependent queries

Gate a query on another result with enabled rather than firing it with undefined input.

  • The enabled flag holds a query until its input actually exists.
  • A disabled query reports pending rather than error, so the UI stays honest.
  • Chaining too many dependent queries rebuilds the waterfall the cache removed.

Mutations

useMutation

Model a write as an explicit action with its own pending, error and success states.

  • A mutation does not run on render; it runs when something calls mutate.
  • Pending state belongs to the action, so a button can disable itself.
  • The async form lets a handler await the result and then navigate.

Optimistic updates

Apply the expected result immediately and keep a snapshot to restore if the write fails.

  • The screen updates before the server answers, which is what makes it feel instant.
  • Cancel in-flight reads first, or a refetch overwrites the optimistic value.
  • Keep the snapshot: without it a failed write has nothing to roll back to.

onMutate, onError, onSettled

Use the lifecycle to cancel in-flight reads, roll back and then resynchronize.

  • onMutate runs before the request and returns the rollback context.
  • onError receives that context and restores the previous cache contents.
  • onSettled runs either way, which is where invalidation belongs.

Mutation keys and scoping

Name mutations so concurrent writes can be tracked or serialized deliberately.

  • A key lets other components observe a write they did not start.
  • A scope serialises writes that share it, instead of letting them race.
  • Unkeyed mutations are fine until two of them touch the same record.

Retry semantics for writes

Understand why a non-idempotent write should not retry the way a read does.

  • A retried create can produce two records, unlike a retried read.
  • Retry only when the server treats a repeated request as the same operation.
  • An idempotency key is what makes a safe retry possible at all.

Invalidation after a write

Decide which query keys a mutation makes stale rather than refetching everything.

  • Name the keys the write actually affects rather than clearing everything.
  • Over-invalidating turns one save into a burst of refetches.
  • Under-invalidating leaves a stale list beside a freshly saved detail view.

Pagination & streaming

Paginated queries

Keep the previous page visible while the next one loads instead of flashing empty.

  • Keeping the previous page visible avoids a layout jump on every click.
  • Each page is its own cache entry, so going back is instant.
  • Prefetch the next page while the reader is still on the current one.

placeholderData

Show a stand-in value that is never written to the cache as if it were real data.

  • Placeholder data renders immediately but is never written to the cache.
  • Derive it from the previous page, or from a list row already fetched.
  • Initial data is different: it is treated as real, cached data.

useInfiniteQuery

Accumulate pages with getNextPageParam and render a growing list from page data.

  • Pages accumulate in one entry rather than replacing one another.
  • The next page parameter is derived from the last page, usually a cursor.
  • Returning undefined from it is what tells the list it has reached the end.

Bidirectional infinite lists

Fetch older and newer pages from the same entry when the feed grows at both ends.

  • A chat log grows at both ends, so it needs a previous page as well as a next.
  • Prepending must adjust scroll position or the view jumps as content arrives.
  • Cap the retained pages, since an unbounded feed grows without limit.

Prefetching

Warm a cache entry before navigation so the next screen renders without a spinner.

  • Prefetch on hover or on intent, so the data is there when the click lands.
  • The fetch is skipped when a fresh entry already exists.
  • Prefetching everything on a page costs bandwidth that the reader may never use.

Cancellation

Abort a superseded request with the signal the query function receives.

  • The abort signal ends a request that a newer one has already superseded.
  • Fetchers must pass the signal through or cancellation has no effect.
  • Cancelling before an optimistic write stops a stale response from landing.

Server rendering & testing

Hydration boundaries

Dehydrate prefetched entries on the server and hydrate them on the client without refetching.

  • The server serialises what it fetched and the client adopts it as cache.
  • Without hydration the client refetches everything the server already had.
  • Only serialisable data survives, so class instances need a transformer.

Per-request clients

Create a fresh client per server request so no user sees another user's cached data.

  • A module-level client on the server would share one cache between users.
  • Creating it per request keeps one visitor's data out of another's response.
  • The same rule applies to any per-user default set on the client.

Suspense mode

Let a boundary render the pending state instead of branching on status in the component.

  • The boundary decides the fallback, so sibling components can share one.
  • Components read data directly, with no status branch in the markup.
  • A boundary placed too high blanks more of the page than it needs to.

Error boundaries

Throw query errors to a boundary when a screen cannot render without the data.

  • Throwing on error moves failure handling out of every component.
  • A reset lets the reader retry without reloading the page.
  • Keep inline error handling where a partial screen is still useful.

Testing queries

Disable retries and use a fresh client per test so failures surface fast and stay isolated.

  • A client created inside the test wrapper cannot leak between cases.
  • Retries on turn a one-line failure into a multi-second timeout.
  • Mock at the network boundary so the real fetcher and key are exercised.

Devtools

Inspect keys, staleness and observers to explain a refetch you did not expect.

  • The panel lists every entry with its key, status and observer count.
  • An entry with no observers explains why a refetch never happens.
  • Two entries that should be one usually means an unstable key.

Sources

  • TanStack Query official documentation

Ask follow-up questions on any topic in your dashboard.

Start learning