From 9b5b7809a91584f838fc88e538b59dc531b839b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E8=83=9C?= <2318857637@qq.com>
Date: Thu, 11 Jun 2026 20:52:54 +0800
Subject: [PATCH] fix: retry remounted queries after error reset
---
.changeset/error-boundary-remount-retry.md | 5 +
.../src/QueryErrorResetBoundary.tsx | 8 ++
.../QueryResetErrorBoundary.test.tsx | 99 +++++++++++++++++++
.../react-query/src/errorBoundaryUtils.ts | 65 +++++++++++-
4 files changed, 176 insertions(+), 1 deletion(-)
create mode 100644 .changeset/error-boundary-remount-retry.md
diff --git a/.changeset/error-boundary-remount-retry.md b/.changeset/error-boundary-remount-retry.md
new file mode 100644
index 00000000000..f10155ae037
--- /dev/null
+++ b/.changeset/error-boundary-remount-retry.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/react-query': patch
+---
+
+Preserve error boundary reset retries for remounted queries when another query clears the reset state first.
diff --git a/packages/react-query/src/QueryErrorResetBoundary.tsx b/packages/react-query/src/QueryErrorResetBoundary.tsx
index 910215bcb6d..70e2f6ff8c8 100644
--- a/packages/react-query/src/QueryErrorResetBoundary.tsx
+++ b/packages/react-query/src/QueryErrorResetBoundary.tsx
@@ -5,21 +5,29 @@ import * as React from 'react'
export type QueryErrorResetFunction = () => void
export type QueryErrorIsResetFunction = () => boolean
export type QueryErrorClearResetFunction = () => void
+export type QueryErrorResetCountFunction = () => number
export interface QueryErrorResetBoundaryValue {
clearReset: QueryErrorClearResetFunction
+ getResetCount?: QueryErrorResetCountFunction
isReset: QueryErrorIsResetFunction
reset: QueryErrorResetFunction
}
function createValue(): QueryErrorResetBoundaryValue {
let isReset = false
+ let resetCount = 0
+
return {
clearReset: () => {
isReset = false
},
+ getResetCount: () => {
+ return resetCount
+ },
reset: () => {
isReset = true
+ resetCount++
},
isReset: () => {
return isReset
diff --git a/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.tsx b/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.tsx
index 21d594129fb..89d5e2ce332 100644
--- a/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.tsx
+++ b/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.tsx
@@ -9,6 +9,7 @@ import {
QueryErrorResetBoundary,
useQueries,
useQuery,
+ useQueryErrorResetBoundary,
useSuspenseQueries,
useSuspenseQuery,
} from '..'
@@ -91,6 +92,104 @@ describe('QueryErrorResetBoundary', () => {
consoleMock.mockRestore()
})
+ it('should retry a remounted error query when another query mounted after reset', async () => {
+ const consoleMock = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => undefined)
+ const errorKey = queryKey()
+ const otherKey = queryKey()
+
+ let succeed = false
+
+ const queryFn = vi.fn(() =>
+ sleep(10).then(() => {
+ if (!succeed) {
+ throw new Error('Error')
+ }
+ return 'data'
+ }),
+ )
+
+ function ErrorFallback() {
+ const { reset } = useQueryErrorResetBoundary()
+
+ React.useEffect(() => {
+ return () => {
+ reset()
+ }
+ }, [reset])
+
+ return
error boundary
+ }
+
+ function ErrorPage() {
+ const { data } = useSuspenseQuery({
+ queryKey: errorKey,
+ queryFn,
+ retry: false,
+ retryOnMount: true,
+ })
+
+ return {data}
+ }
+
+ function OtherPage() {
+ const { data } = useSuspenseQuery({
+ queryKey: otherKey,
+ queryFn: () => sleep(10).then(() => 'other'),
+ })
+
+ return {data}
+ }
+
+ function App() {
+ const [showErrorPage, setShowErrorPage] = React.useState(true)
+
+ return (
+
+
+ {showErrorPage ? (
+ }>
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+ )
+ }
+
+ const rendered = renderWithClient(
+ queryClient,
+
+
+ ,
+ )
+
+ await act(() => vi.advanceTimersByTimeAsync(10))
+ expect(rendered.getByText('error boundary')).toBeInTheDocument()
+ expect(queryFn).toHaveBeenCalledTimes(1)
+
+ fireEvent.click(rendered.getByText('toggle'))
+ await act(() => vi.advanceTimersByTimeAsync(10))
+ expect(rendered.getByText('other')).toBeInTheDocument()
+
+ succeed = true
+
+ fireEvent.click(rendered.getByText('toggle'))
+ await act(() => vi.advanceTimersByTimeAsync(10))
+ expect(rendered.getByText('data')).toBeInTheDocument()
+ expect(queryFn).toHaveBeenCalledTimes(2)
+
+ consoleMock.mockRestore()
+ })
+
it('should not throw error if query is disabled', async () => {
const consoleMock = vi
.spyOn(console, 'error')
diff --git a/packages/react-query/src/errorBoundaryUtils.ts b/packages/react-query/src/errorBoundaryUtils.ts
index 734cc74d3de..812b7cbfb53 100644
--- a/packages/react-query/src/errorBoundaryUtils.ts
+++ b/packages/react-query/src/errorBoundaryUtils.ts
@@ -10,6 +10,69 @@ import type {
} from '@tanstack/query-core'
import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
+const queryResetCounts = new WeakMap<
+ QueryErrorResetBoundaryValue,
+ WeakMap