In TanStack Query v5, what does the refetchType option of invalidateQueries control, and what happens to matched queries that no component is observing?
answer
- marking and refetching are separate
- default is the mounted ones
- four values including none
- stale wins at next mount
basics
~10 sEvery 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
Recall that invalidateQueries refetches only on-screen queries by default and that off-screen matches refetch when they are next shown.
Explain the mark-then-refetch split, the four refetchType values, what active means, and why disabled and static queries are skipped.
Choose between invalidate, refetch, reset and remove for real flows such as logout, and use 'all' or 'none' deliberately rather than by habit.
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.