skip to content

In TanStack Query v5, what does the refetchType option of invalidateQueries control, and what happens to matched queries that no component is observing?

level: middleimportance: should knowfreq 42%

answer

  1. marking and refetching are separate
  2. default is the mounted ones
  3. four values including none
  4. stale wins at next mount

basics

~10 s

Every matched query is marked invalidated; refetchType only chooses which of them refetch now: 'active' (default), 'inactive', 'all' or 'none'. Unobserved matches are left stale and refetch when a component next mounts them.

solid answer

~40 s

`invalidateQueries` has two steps: mark every matched query invalidated, then refetch some of them. `refetchType` picks the second set. `'active'` - the default - refetches only queries with at least one enabled observer, that is, what is on screen. `'inactive'` refetches only the unobserved ones, `'all'` refetches both, and `'none'` refetches nothing, so the call only marks. An unobserved query that was marked stays in the cache with its data; because an invalidated query counts as stale whatever its `staleTime`, it refetches when a component mounts it again. Two kinds of query are never refetched by this step: disabled ones and ones with `staleTime: 'static'`. Use `'all'` when an off-screen query must be warm before the user navigates, and `'none'` when you want to mark now and let normal triggers do the fetching.

go deeper

for a junior

Recall that invalidateQueries refetches only on-screen queries by default and that off-screen matches refetch when they are next shown.

for a middle

Explain the mark-then-refetch split, the four refetchType values, what active means, and why disabled and static queries are skipped.

for a senior

Choose between invalidate, refetch, reset and remove for real flows such as logout, and use 'all' or 'none' deliberately rather than by habit.

for a principal

Decide how eagerly the app pre-warms hidden screens after writes, weighing extra backend load against the latency users see on navigation.

## Two steps inside `invalidateQueries` In TanStack Query v5, `queryClient.invalidateQueries(filters)` does two separate things: 1. **Mark**: every query that matches the filters is set to *invalidated*. An invalidated query is stale regardless of its `staleTime` (with one exception, `'static'`, below). 2. **Refetch**: some of the marked queries are refetched right away, through the same machinery as `refetchQueries`. The **`refetchType`** option decides step 2 only. Step 1 always applies to every match. ## The four values | `refetchType` | Refetched immediately | Typical use | |---|---|---| | `'active'` (default) | matched queries with at least one enabled observer | the normal case: update what the user sees | | `'inactive'` | matched queries with no enabled observer | rare: warm hidden screens without touching visible ones | | `'all'` | every matched query | the next screen must already be fresh when the user gets there | | `'none'` | nothing | mark now, let mount/focus/interval triggers fetch later | **Active** has a precise meaning: at least one observer - usually a mounted `useQuery` - with `enabled` not false. A query whose only observers are disabled is *not* active. If `refetchType` is not given, the method falls back to the filters' `type`, then to `'active'`. ## What happens to the ones not refetched An unobserved (inactive) query that was matched: - keeps its cached data - invalidation never deletes anything; - is now stale, even if its `staleTime` has not elapsed; - refetches the next time a component mounts it, because the default `refetchOnMount: true` refetches stale data on mount. The cached data renders immediately and is replaced when the refetch completes; - can still be garbage-collected after `gcTime` if nothing mounts it in time - in which case the next mount is a cold load anyway. ## Queries the refetch step skips Even with `refetchType: 'all'`, two kinds of query are not refetched: - **disabled queries** (`enabled: false`, or a `skipToken` query function) - they are marked but never fetched by this step; - **static queries** - an observer with `staleTime: 'static'` means "this data never goes stale", and both invalidation's refetch and `refetchQueries` leave it alone. ## Related methods, for contrast `invalidateQueries` is one of four ways to act on matched queries. Interviewers often ask for the differences, for example in a logout flow: | Method | Keeps data? | Marks stale? | Refetches | |---|---|---|---| | `invalidateQueries` | yes | yes | per `refetchType`, default active | | `refetchQueries` | yes | no | every match that is not disabled or static, stale or not (filter `type` defaults to `'all'`) | | `resetQueries` | no - back to initial state (e.g. `initialData`) | n/a | active matches | | `removeQueries` | no - deleted from the cache | n/a | none | - **After a write**, `invalidateQueries` is the default: it respects what is on screen and defers the rest. - **On logout**, `removeQueries` (or `queryClient.clear()`) drops another user's data; invalidating would keep it cached and show it until the refetch lands. - **To force a fetch that ignores staleness**, `refetchQueries` - but without filters it refetches every eligible query in the cache. ## The returned promise `invalidateQueries` returns a promise that resolves when the refetches it started have settled. With `refetchType: 'none'` there is nothing to wait for, so it resolves immediately. The promise does not reject when an individual refetch fails, unless `throwOnError: true` is passed in the second (options) argument. Also in that argument, `cancelRefetch` defaults to `true`: if a matched query that already has data is mid-fetch, that fetch is cancelled and started again, so the result reflects the write that triggered the invalidation. A first load still in flight (no data yet) is reused rather than restarted. ```ts await queryClient.invalidateQueries( { queryKey: ['reports'], refetchType: 'all' }, { throwOnError: true }, ) ``` ## Picking a value in practice - **Leave the default** after ordinary writes: visible data updates now, hidden data updates when it is shown. - **Use `'all'`** when the write is followed by navigation to a screen whose query is currently unmounted, and a cached-then-refetched flash there would confuse users. - **Use `'none'`** inside loops of writes, then refetch once at the end, to avoid one refetch per write. - **Reach for `'inactive'`** almost never; it exists for completeness.

  • When would you use refetchType: 'none'?
    When you want the data marked stale but no request now - for example a batch of writes where you invalidate after each one and trigger one refetch at the end, or data that should simply refresh the next time the user opens that screen.
  • A query is on screen but its only useQuery has enabled: false. Is it refetched by the default invalidation?
    No. Active means at least one observer with `enabled` not false, and the refetch step also skips disabled queries. It is marked invalidated, so it will fetch once it becomes enabled and a refetch trigger fires.

saying these in an interview costs you the question

  • Believes refetchType also decides which matched queries get marked stale.
  • Thinks invalidation deletes the data of queries that are not on screen.
  • Expects refetchType all to refetch disabled queries too.
  • Uses invalidateQueries on logout and expects the previous user's data to disappear.
  • Assumes refetchQueries only refetches stale queries.