Repository navigation
QueryClient.isFetching and useIsFetching accept a fetchStatus filter and ignore it #11984
Description
Describe the bug
QueryFilters has a fetchStatus?: FetchStatus field, and FetchStatus is 'fetching' | 'paused' | 'idle', so isFetching({ fetchStatus: 'paused' }) type-checks. It has no effect: QueryClient.isFetching spreads the filters it is given and then writes its own fetchStatus over them, so the argument is discarded for every value, not only 'paused'.
QueryCache.findAll honours the same filter correctly, which makes the difference easy to miss — the filter works through one entry point and is silently dropped through the other.
Minimal reproduction
import { QueryClient } from '@tanstack/query-core'
const client = new QueryClient()
const queryKey = ['session']
const cache = client.getQueryCache()
const query = cache.build(client, { queryKey, queryFn: async () => 'x' })
query.setState({ fetchStatus: 'paused' })
console.log(cache.find({ queryKey })?.state.fetchStatus) // 'paused'
console.log(cache.findAll({ queryKey, fetchStatus: 'paused' }).length) // 1
console.log(client.isFetching({ queryKey, fetchStatus: 'paused' })) // 0
console.log(client.isFetching({ queryKey, fetchStatus: 'idle' })) // 0Expected / actual
| call | expected | actual |
|---|---|---|
findAll({ fetchStatus: 'paused' }).length |
1 | 1 |
isFetching({ fetchStatus: 'paused' }) |
1, or a type error | 0 |
isFetching({ fetchStatus: 'idle' }) |
1, or a type error | 0 |
Either outcome would be fine. What is awkward is a public, typed, documented parameter that is accepted and does nothing.
Cause
packages/query-core/src/queryClient.ts:
isFetching(filters) {
return this.#queryCache.findAll({ ...filters, fetchStatus: 'fetching' }).length
}useIsFetching forwards its filters to this method, so it behaves the same way.
Why it came up
An app that asks "is this query still waiting on the network" wants paused to count: a lookup held because the browser is offline, or because the window is unfocused and the retry is deferred, has not answered yet. Reading it through useIsFetching({ queryKey, fetchStatus: 'paused' }) compiles and quietly answers 0, so the UI treats a waiting query as a settled one. In our case a page showed a "not found" state to someone whose session lookup was merely paused.
The workaround is to subscribe to the cache and use findAll directly, which works but means reimplementing what useIsFetching exists to provide.
Possible resolutions
In rough order of how little they change:
- Type it out — omit
fetchStatusfrom the filters these two accept, so passing it is a compile error, and say in the reference that they count fetching queries only. - Honour it — use the caller's
fetchStatuswhen one is given and default to'fetching'otherwise. This changes behaviour for anyone currently passing a value and relying on it being ignored, which seems unlikely but is a real semver consideration. - Leave the behaviour and document it — note on
useIsFetchingandQueryClient.isFetchingthatfetchStatusis fixed, and point atgetQueryCache().findAllfor other statuses.
Happy to send a PR for whichever you prefer.
Related
- Discussion Proposal for queryClient.isPaused feature #6756 proposes a built-in
queryClient.isPaused/useIsPaused, and its example hook uses the same spread-then-override pattern in user code. If that proposal is the intended answer, this is the part of it that bites today — and option 1 or 3 above would stop the current API misleading people in the meantime. - useIsFetching stops tracking queries immediately #7559 and Incorrect isFetching state when using the PersistQueryClient #8531 are about
isFetchingreporting the wrong thing for fetching queries; this is narrower, about a filter argument being inert.
Environment
@tanstack/query-core5.104.1- Node 26.11.1, TypeScript 6.0.3
- Reproduced with
query-corealone, so no framework adapter is involved
Activity