From 13f6d29219006594e67d3e7b64462e2814dfe211 Mon Sep 17 00:00:00 2001 From: Shaurya Srivastava Date: Tue, 19 May 2026 21:10:29 -0700 Subject: [PATCH] fix(react-query): do not report isFetching when subscribed is false When useQuery or useQueries is called with subscribed: false, the observer never subscribes to the query, so no fetch is ever triggered. Previously the optimistic result still advertised fetchStatus 'fetching', causing isFetching to be true while the cache state remained idle. The optimistic fetching state is now skipped in that case so the returned result matches the underlying query state. Fixes #10718 --- .changeset/fix-subscribed-isfetching.md | 5 +++ .../src/__tests__/useQuery.test.tsx | 34 +++++++++++++++++++ packages/react-query/src/useBaseQuery.ts | 7 +++- packages/react-query/src/useQueries.ts | 11 ++++-- 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-subscribed-isfetching.md diff --git a/.changeset/fix-subscribed-isfetching.md b/.changeset/fix-subscribed-isfetching.md new file mode 100644 index 00000000000..ff0643320a0 --- /dev/null +++ b/.changeset/fix-subscribed-isfetching.md @@ -0,0 +1,5 @@ +--- +'@tanstack/react-query': patch +--- + +Fix `isFetching` reporting `true` when `subscribed: false` and no fetch is actually running. `useBaseQuery` and `useQueries` no longer apply the `optimistic` fetching state when the observer will not subscribe, so `isFetching` and `fetchStatus` now stay consistent with the underlying query cache. diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx index 7504b9b4cb0..e8b30ba722b 100644 --- a/packages/react-query/src/__tests__/useQuery.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.test.tsx @@ -5980,6 +5980,40 @@ describe('useQuery', () => { expect(renders).toBe(1) }) + + it('should not report isFetching=true when subscribed is false and no fetch is running', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + const states: Array> = [] + + function Page() { + const state = useQuery({ + queryKey: key, + queryFn, + subscribed: false, + }) + states.push(state) + return ( +
+ + fetchStatus: {state.fetchStatus} isFetching:{' '} + {String(state.isFetching)} + +
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(0) + rendered.getByText('fetchStatus: idle isFetching: false') + + // No fetch is or will be running, so the observer must not advertise + // an optimistic fetching state. + expect(states.every((s) => s.isFetching === false)).toBe(true) + expect(states.every((s) => s.fetchStatus === 'idle')).toBe(true) + expect(queryFn).toHaveBeenCalledTimes(0) + }) }) it('should have status=error on mount when a query has failed', async () => { diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts index a88f7d40fbf..8cc1b4954c0 100644 --- a/packages/react-query/src/useBaseQuery.ts +++ b/packages/react-query/src/useBaseQuery.ts @@ -75,9 +75,14 @@ export function useBaseQuery< } // Make sure results are optimistically set in fetching state before subscribing or updating options + // When `subscribed: false`, the observer will never subscribe, so no fetch will be triggered. + // In that case we must not flip the optimistic result into the fetching state, otherwise + // `isFetching` would be reported as `true` even though no fetch is or will be happening. defaultedOptions._optimisticResults = isRestoring ? 'isRestoring' - : 'optimistic' + : options.subscribed === false + ? undefined + : 'optimistic' ensureSuspenseTimers(defaultedOptions) ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary, query) diff --git a/packages/react-query/src/useQueries.ts b/packages/react-query/src/useQueries.ts index de179837a5f..1e59605cd95 100644 --- a/packages/react-query/src/useQueries.ts +++ b/packages/react-query/src/useQueries.ts @@ -224,6 +224,7 @@ export function useQueries< const isRestoring = useIsRestoring() const errorResetBoundary = useQueryErrorResetBoundary() + const subscribed = options.subscribed !== false const defaultedQueries = React.useMemo( () => queries.map((opts) => { @@ -231,14 +232,18 @@ export function useQueries< opts as QueryObserverOptions, ) - // Make sure the results are already in fetching state before subscribing or updating options + // Make sure the results are already in fetching state before subscribing or updating options. + // When `subscribed: false`, the observer will never subscribe, so no fetch will be triggered. + // Skip the optimistic fetching state in that case so `isFetching` does not lie. defaultedOptions._optimisticResults = isRestoring ? 'isRestoring' - : 'optimistic' + : subscribed + ? 'optimistic' + : undefined return defaultedOptions }), - [queries, client, isRestoring], + [queries, client, isRestoring, subscribed], ) defaultedQueries.forEach((queryOptions) => {