diff --git a/.changeset/reset-queries-refetch-matched.md b/.changeset/reset-queries-refetch-matched.md new file mode 100644 index 0000000000..e46c04cc62 --- /dev/null +++ b/.changeset/reset-queries-refetch-matched.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-core': patch +--- + +Fix `resetQueries` not refetching queries selected by a state-dependent filter (e.g. `predicate: (query) => query.state.status === 'error'`). `resetQueries` reset the matched queries first — mutating their status (`error` → `pending`) — and then re-ran the same filter to choose refetch targets, so the now-changed queries no longer matched and were never refetched. The matched queries are now snapshotted before the reset and refetched directly. diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index c09db30446..1db6f581ba 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -1692,6 +1692,41 @@ describe('queryClient', () => { expect(queryFn2).toHaveBeenCalledTimes(0) expect(didSkipTokenRun).toBe(false) }) + + it('should refetch queries matched by a state-dependent predicate after reset (#10705)', async () => { + const key = queryKey() + let shouldFail = true + const queryFn = vi.fn(() => + sleep(10).then(() => { + if (shouldFail) throw new Error('failed') + return 'data' + }), + ) + + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn, + retry: false, + }) + const unsubscribe = observer.subscribe(() => undefined) + + // let the initial fetch settle into an error + await vi.advanceTimersByTimeAsync(10) + expect(queryClient.getQueryState(key)?.status).toBe('error') + expect(queryFn).toHaveBeenCalledTimes(1) + + // the refetch triggered by the reset should now succeed + shouldFail = false + const resetPromise = queryClient.resetQueries({ + predicate: (query) => query.state.status === 'error', + }) + await vi.advanceTimersByTimeAsync(10) + await resetPromise + + unsubscribe() + expect(queryFn).toHaveBeenCalledTimes(2) + expect(queryClient.getQueryState(key)?.status).toBe('success') + }) }) describe('focusManager and onlineManager', () => { diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index d82106c737..72200a8ca6 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -260,13 +260,21 @@ export class QueryClient { const queryCache = this.#queryCache return notifyManager.batch(() => { - queryCache.findAll(filters).forEach((query) => { + // Snapshot the matched queries *before* resetting them. `reset()` mutates + // each query (e.g. status 'error' -> 'pending'), so re-running `filters` + // afterwards could miss them — e.g. a `predicate` filtering on status, see + // #10705. Refetch exactly the queries that matched before the reset. + const queries = queryCache.findAll(filters) + + queries.forEach((query) => { query.reset() }) + + const queriesToRefetch = new Set(queries) return this.refetchQueries( { type: 'active', - ...filters, + predicate: (query) => queriesToRefetch.has(query), }, options, )