In TanStack Query v5, what do a query's status and fetchStatus each describe, and which combinations of the two can occur?
answer
- two independent axes
- data versus queryFn
- pending, error, success
- fetching, paused, idle
- isLoading is a combination
basics
~20 sstatus 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 sIn 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
Recall the two fields and their three values each, and that status is about data while fetchStatus is about the queryFn running.
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.
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.
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.