From 9b318fb875e833c9e7d61e406d3b408934b47c06 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Sat, 23 May 2026 00:02:39 +0200 Subject: [PATCH 1/2] fix(react): do not go into optimistic fetching state when not subscribed --- .../src/__tests__/useQuery.test.tsx | 36 +++++++++++++++++++ packages/react-query/src/useBaseQuery.ts | 8 +++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx index 7504b9b4cb0..47a060067fc 100644 --- a/packages/react-query/src/__tests__/useQuery.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.test.tsx @@ -5916,6 +5916,42 @@ describe('useQuery', () => { ).toBe(1) }) + it('should not optimistically show fetching when unsubscribed', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + function Page() { + const [subscribed, setSubscribed] = React.useState(true) + const query = useQuery({ + queryKey: key, + queryFn, + subscribed, + }) + + return ( +
+ isFetching: {String(query.isFetching)} + fetchStatus: {query.fetchStatus} + +
+ ) + } + + const rendered = renderWithClient(queryClient, ) + await vi.advanceTimersByTimeAsync(0) + rendered.getByText('isFetching: false') + rendered.getByText('fetchStatus: idle') + + fireEvent.click(rendered.getByRole('button', { name: 'unsubscribe' })) + + expect(queryFn).toHaveBeenCalledTimes(1) + expect( + queryClient.getQueryCache().find({ queryKey: key })!.observers.length, + ).toBe(0) + rendered.getByText('isFetching: false') + rendered.getByText('fetchStatus: idle') + }) + it('should not be attached to the query when subscribed is false', async () => { const key = queryKey() const queryFn = vi.fn(() => Promise.resolve('data')) diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts index a88f7d40fbf..eb6a40d9a23 100644 --- a/packages/react-query/src/useBaseQuery.ts +++ b/packages/react-query/src/useBaseQuery.ts @@ -74,10 +74,14 @@ export function useBaseQuery< } } + const subscribed = options.subscribed !== false + // Make sure results are optimistically set in fetching state before subscribing or updating options defaultedOptions._optimisticResults = isRestoring ? 'isRestoring' - : 'optimistic' + : subscribed + ? 'optimistic' + : undefined ensureSuspenseTimers(defaultedOptions) ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary, query) @@ -99,7 +103,7 @@ export function useBaseQuery< // note: this must be called before useSyncExternalStore const result = observer.getOptimisticResult(defaultedOptions) - const shouldSubscribe = !isRestoring && options.subscribed !== false + const shouldSubscribe = !isRestoring && subscribed React.useSyncExternalStore( React.useCallback( (onStoreChange) => { From 461fb084cc33d04ad5fa6b2ea33c0dd9894f47f9 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Sat, 23 May 2026 00:04:48 +0200 Subject: [PATCH 2/2] changeset --- .changeset/strong-toes-knock.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-toes-knock.md diff --git a/.changeset/strong-toes-knock.md b/.changeset/strong-toes-knock.md new file mode 100644 index 00000000000..42f1258ecd1 --- /dev/null +++ b/.changeset/strong-toes-knock.md @@ -0,0 +1,5 @@ +--- +'@tanstack/react-query': patch +--- + +fix(react-query): do not go into optimistic fetching state when not subscribed