skip to content

In TanStack Query v5, what do a query's status and fetchStatus each describe, and which combinations of the two can occur?

level: middleimportance: must knowfreq 72%

answer

  1. two independent axes
  2. data versus queryFn
  3. pending, error, success
  4. fetching, paused, idle
  5. isLoading is a combination

basics

~20 s

status answers whether the query has data: pending, error or success. fetchStatus answers whether the queryFn is running: fetching, paused or idle. They vary independently, so success can pair with fetching and pending can pair with idle or paused.

solid answer

~40 s

In v5 a query result carries two fields. `status` is about **data**: `pending` (no data and no finished attempt), `error` (the last attempt failed) or `success` (data is available). `fetchStatus` is about **the queryFn**: `fetching` (running now), `paused` (it wanted to run but the network mode held it back) or `idle`. Because background refetches exist, most combinations occur: `success` + `fetching` is a background refetch, `pending` + `idle` is a disabled query, `pending` + `paused` is a first load waiting for a connection. The flags are derived from these two: `isPending` and `isSuccess` from `status`, `isFetching` and `isPaused` from `fetchStatus`, `isLoading` = `isPending && isFetching`, `isRefetching` = `isFetching && !isPending`.

go deeper

for a junior

Recall the two fields and their three values each, and that status is about data while fetchStatus is about the queryFn running.

for a middle

Walk through the combinations, especially success with fetching and pending with idle, and derive isLoading and isRefetching from the two fields without looking them up.

for a senior

Map each flag to a UI decision: full loading state, background indicator, error with or without fallback data, and show how the wrong flag causes blanking or stuck spinners.

for a principal

Argue for a shared rendering convention across teams built on these flags, so every screen treats background refetches and refetch errors the same way.

## Two questions, two fields A TanStack Query v5 query result answers two different questions, and it uses a separate field for each: - **`status`** - *do I have data?* Values: `'pending'`, `'error'`, `'success'`. - **`fetchStatus`** - *is the query function running?* Values: `'fetching'`, `'paused'`, `'idle'`. The split exists because TanStack Query keeps showing cached data while it refetches in the background. A single "loading" enum cannot say "I have data **and** I am fetching", so v5 keeps the two concerns apart and lets them vary independently. `status` definitions, as documented in the source: 1. **`pending`** - there is no cached data and no query attempt has finished yet. 2. **`error`** - the most recent attempt failed (after its retries). 3. **`success`** - the query has received data and can render it. `fetchStatus` definitions: 1. **`fetching`** - the `queryFn` is executing: the first load or a background refetch. 2. **`paused`** - the query wanted to fetch but was held back, typically because the default network mode believes the device is offline. 3. **`idle`** - nothing is running. ## Which combinations occur | `status` | `fetchStatus` | What it means | |---|---|---| | `pending` | `fetching` | first load in flight - the only case where `isLoading` is true | | `pending` | `paused` | first load wanted, but held until the connection returns | | `pending` | `idle` | disabled (for example `enabled: false`) and no data yet | | `success` | `idle` | data on screen, nothing running | | `success` | `fetching` | background refetch while the old data stays visible | | `success` | `paused` | a refetch was wanted while offline; old data still shown | | `error` | `idle` | the last attempt failed and nothing is running | | `error` | `fetching` | a refetch is running for a query that still holds earlier data and whose last attempt failed | A detail on the last row: when a fetch starts for a query that has **no** data, the query goes back to `status: 'pending'` and its `error` is cleared. So an `error` + `fetching` pair shows up only for a query that still has earlier data. ## The derived boolean flags The `is*` flags are shorthands computed from the two fields: | Flag | Derived from | |---|---| | `isPending` | `status === 'pending'` | | `isSuccess`, `isError` | `status` | | `isFetching` | `fetchStatus === 'fetching'` | | `isPaused` | `fetchStatus === 'paused'` | | `isLoading` | `isPending && isFetching` | | `isRefetching` | `isFetching && !isPending` | | `isLoadingError` | error and no data | | `isRefetchError` | error while data is still present | ## Why it matters in a component Choosing the wrong flag produces a spinner at the wrong time: - **`isFetching` for the main loading state** blanks the screen on every background refetch, even though cached data is available. - **`isPending` for a query that can be disabled or paused** shows a spinner while no request is running at all. - **`isError` checked before `data`** hides perfectly good cached data the moment one background refetch fails, because `status` becomes `'error'` while `data` still holds the last success. A common pattern is: render `data` whenever it is defined, show a small background indicator on `isFetching`, show the full-page loading state only on `isLoading`, and show an error panel only when there is no data to fall back to (`isLoadingError`), with an inline warning for `isRefetchError`. ## Version history (v4 to v5) v5 renamed things in a way that still trips people reading older posts: - the `status` value `'loading'` became **`'pending'`**, and the flag `isLoading` became **`isPending`**; - a new **`isLoading`** was introduced as `isPending && isFetching` - what v4 called `isInitialLoading`, which v5 keeps only as a deprecated alias. So "`isLoading` means no data yet" is v4 vocabulary. In v5, a disabled query with no data is `isPending: true` but `isLoading: false`. ## A quick self-test Name the pair of values for each situation before reading the answer: 1. A list query has cached rows and the user refocuses the tab, which starts a refetch - **`success` + `fetching`**, so `isRefetching` is true and `isLoading` is false. 2. A details query has `enabled: false` because no row is selected yet - **`pending` + `idle`**, so `isPending` is true while `isFetching` is false. 3. A first load has failed twice and is waiting for its third retry - still **`pending` + `fetching`**, with `failureCount` at 2 and `error` still `null`. If all three come out right without looking, the two-axis model is in place.

  • Which flag would you use for a small 'updating' indicator next to a list that already has data?
    `isRefetching`, which is `isFetching && !isPending`: true only when a fetch runs for a query that already has data. `isFetching` alone would also fire during the first load, where the full loading state is already showing.
  • Does a query go back to status 'pending' when a background refetch starts?
    Not if it has data. A fetch resets `status` to `'pending'` only when `data` is `undefined`; a query with cached data keeps `'success'` (or `'error'`) and only its `fetchStatus` changes to `'fetching'`.

A parcel tracker shows two things at once: whether the parcel has ever been delivered (status) and whether a van is on the road right now (fetchStatus). A delivered parcel can still have a van out with a replacement.

saying these in an interview costs you the question

  • Treats isFetching as the first-load flag and blanks the page on every background refetch.
  • Says isLoading in v5 means no data yet, which was the v4 meaning.
  • Believes status returns to pending whenever a refetch starts.
  • Thinks status error implies data is undefined.
  • Assumes fetchStatus paused only happens when a developer pauses a query manually.