From a1228a29271061a960f878769170cbabcee1b207 Mon Sep 17 00:00:00 2001 From: devchangjun Date: Mon, 13 Jul 2026 00:41:46 +0900 Subject: [PATCH] Guard consistent fetch results during invalidation Cover the stale fetchQuery invalidation scenario with both active and inactive observers. Silent cancellation now follows the replacement fetch while inactive queries retain their original fetch, and both paths resolve to fresh data. Constraint: Preserve the silent cancellation contract introduced by #9293 Rejected: Force CancelledError rejection | current query-core behavior intentionally piggybacks replacement fetches Confidence: high Scope-risk: narrow Tested: query-core 548 tests; queryClient 109 tests; ESLint; TypeScript current; Prettier Not-tested: legacy TypeScript matrix due local Corepack pnpm.cjs EACCES Related: TanStack/query#8060 --- .../src/__tests__/queryClient.test.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index 2ed4c4c193..ce5bb8e9b8 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -1370,6 +1370,37 @@ describe('queryClient', () => { }) describe('invalidateQueries', () => { + it.each([true, false])( + 'should resolve an invalidated imperative fetch when observer active is %s', + async (observerActive) => { + const key = queryKey() + const queryFn = vi.fn(() => sleep(50).then(() => 'updated')) + queryClient.setQueryData(key, 'initial') + + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn, + staleTime: Infinity, + enabled: observerActive, + }) + const unsubscribe = observer.subscribe(() => undefined) + + const promise = queryClient.fetchQuery({ + queryKey: key, + queryFn, + staleTime: 0, + }) + + await vi.advanceTimersByTimeAsync(10) + void queryClient.invalidateQueries({ queryKey: key }) + await vi.advanceTimersByTimeAsync(50) + + await expect(promise).resolves.toBe('updated') + expect(queryFn).toHaveBeenCalledTimes(observerActive ? 2 : 1) + unsubscribe() + }, + ) + it('should refetch active queries by default', async () => { const key1 = queryKey() const key2 = queryKey()