From 18e27443c9a65c9f4733ccd7634fba498bd4b919 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 15 Feb 2026 02:08:23 -0800 Subject: [PATCH 1/2] fix(query-core): handle combine throwing in QueriesObserver#notify When useSuspenseQueries is used with combine and a query transitions to pending/error state (e.g. after resetQueries), the combine function throws because data is undefined despite types narrowing it as defined. The #notify method calls combine as an optimization to check if the combined result changed. If combine throws during this check, we now catch the error and still notify listeners so the framework can re-suspend or show an error boundary. Fixes #10129 --- .../src/__tests__/queriesObserver.test.tsx | 39 +++++++++++++++++++ packages/query-core/src/queriesObserver.ts | 18 ++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/query-core/src/__tests__/queriesObserver.test.tsx b/packages/query-core/src/__tests__/queriesObserver.test.tsx index 88b763f712c..2d78fb93df0 100644 --- a/packages/query-core/src/__tests__/queriesObserver.test.tsx +++ b/packages/query-core/src/__tests__/queriesObserver.test.tsx @@ -630,4 +630,43 @@ describe('queriesObserver', () => { { status: 'success', data: 3 }, ]) }) + + test('should still notify listeners when combine throws after query reset', async () => { + const key1 = queryKey() + const queryFn1 = vi.fn().mockReturnValue({ name: 'test' }) + + const combine = vi.fn( + (results: Array) => { + // This simulates a combine function that assumes data is always defined + // (like useSuspenseQueries types suggest) + return results.map((r) => (r.data as { name: string }).name) + }, + ) + + const observer = new QueriesObserver>( + queryClient, + [{ queryKey: key1, queryFn: queryFn1 }], + { combine }, + ) + + const results: Array> = [] + const unsubscribe = observer.subscribe((result) => { + results.push(result) + }) + + // Wait for queries to resolve + await vi.advanceTimersByTimeAsync(0) + + // Reset the query - this transitions it to pending state + // which should cause combine to throw since data is undefined + queryClient.resetQueries({ queryKey: key1 }) + + // The listener should still have been notified despite combine throwing + const lastResult = results[results.length - 1] + expect(lastResult).toBeDefined() + expect(lastResult![0]!.status).toBe('pending') + expect(lastResult![0]!.data).toBeUndefined() + + unsubscribe() + }) }) diff --git a/packages/query-core/src/queriesObserver.ts b/packages/query-core/src/queriesObserver.ts index 67dd088f9ae..965b425a136 100644 --- a/packages/query-core/src/queriesObserver.ts +++ b/packages/query-core/src/queriesObserver.ts @@ -296,9 +296,23 @@ export class QueriesObserver< if (this.hasListeners()) { const previousResult = this.#combinedResult const newTracked = this.#trackResult(this.#result, this.#observerMatches) - const newResult = this.#combineResult(newTracked, this.#options?.combine) - if (previousResult !== newResult) { + let shouldNotify: boolean + try { + const newResult = this.#combineResult( + newTracked, + this.#options?.combine, + ) + shouldNotify = previousResult !== newResult + } catch { + // If combine throws (e.g. when used with useSuspenseQueries and + // a query transitions to pending/error state after a reset), we + // still need to notify so the framework can re-suspend or show + // an error boundary. + shouldNotify = true + } + + if (shouldNotify) { notifyManager.batch(() => { this.listeners.forEach((listener) => { listener(this.#result) From 074a79459e7869d805dbf7941fae5acf3b3cc671 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 15 Feb 2026 16:14:48 -0800 Subject: [PATCH 2/2] chore: add changeset for combine error handling fix --- .changeset/brave-tigers-wave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/brave-tigers-wave.md diff --git a/.changeset/brave-tigers-wave.md b/.changeset/brave-tigers-wave.md new file mode 100644 index 00000000000..aed947097c2 --- /dev/null +++ b/.changeset/brave-tigers-wave.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-core': patch +--- + +fix: handle combine throwing in QueriesObserver notify