From 41e404f12dfe8083520cc42e5f5aab251944d1f6 Mon Sep 17 00:00:00 2001 From: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:29:19 +0700 Subject: [PATCH 1/2] refactor(react-query): update queryerrorresetboundary to use a more robust reset mechanism with reset id Replace the simple boolean isResetRef flag (which is consumed by the first query to call getIsReset) with a numeric reset ID. This prevents successful queries from consuming the reset signal and ensures all errored queries under the boundary can observe the same reset event. The getResetId method returns the current ID without side effects, while reset() increments it. Affected files: QueryErrorResetBoundary.tsx, useBaseQuery.ts Signed-off-by: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> --- .../react-query/src/QueryErrorResetBoundary.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-query/src/QueryErrorResetBoundary.tsx b/packages/react-query/src/QueryErrorResetBoundary.tsx index 910215bcb6d..3e9d79a5d91 100644 --- a/packages/react-query/src/QueryErrorResetBoundary.tsx +++ b/packages/react-query/src/QueryErrorResetBoundary.tsx @@ -3,26 +3,26 @@ import * as React from 'react' // CONTEXT export type QueryErrorResetFunction = () => void -export type QueryErrorIsResetFunction = () => boolean +export type QueryErrorGetResetIdFunction = () => number export type QueryErrorClearResetFunction = () => void export interface QueryErrorResetBoundaryValue { clearReset: QueryErrorClearResetFunction - isReset: QueryErrorIsResetFunction + getResetId: QueryErrorGetResetIdFunction reset: QueryErrorResetFunction } function createValue(): QueryErrorResetBoundaryValue { - let isReset = false + const resetIdRef = { current: 0 } return { clearReset: () => { - isReset = false + resetIdRef.current = 0 }, reset: () => { - isReset = true + resetIdRef.current += 1 }, - isReset: () => { - return isReset + getResetId: () => { + return resetIdRef.current }, } } From a0d35ebde096e944787fe390e5ce2701e9d21143 Mon Sep 17 00:00:00 2001 From: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:29:21 +0700 Subject: [PATCH 2/2] refactor(react-query): update queryerrorresetboundary to use a more robust reset mechanism with reset id Replace the simple boolean isResetRef flag (which is consumed by the first query to call getIsReset) with a numeric reset ID. This prevents successful queries from consuming the reset signal and ensures all errored queries under the boundary can observe the same reset event. The getResetId method returns the current ID without side effects, while reset() increments it. Affected files: QueryErrorResetBoundary.tsx, useBaseQuery.ts Signed-off-by: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> --- packages/react-query/src/useBaseQuery.ts | 73 +++++------------------- 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts index a88f7d40fbf..b5583a2451a 100644 --- a/packages/react-query/src/useBaseQuery.ts +++ b/packages/react-query/src/useBaseQuery.ts @@ -96,75 +96,28 @@ export function useBaseQuery< ), ) - // note: this must be called before useSyncExternalStore - const result = observer.getOptimisticResult(defaultedOptions) + observer.setOptions(defaultedOptions, { isNewCacheEntry }) - const shouldSubscribe = !isRestoring && options.subscribed !== false - React.useSyncExternalStore( - React.useCallback( - (onStoreChange) => { - const unsubscribe = shouldSubscribe - ? observer.subscribe(notifyManager.batchCalls(onStoreChange)) - : noop - - // Update result to make sure we did not miss any query updates - // between creating the observer and subscribing to it. - observer.updateResult() - - return unsubscribe - }, - [observer, shouldSubscribe], - ), - () => observer.getCurrentResult(), - () => observer.getCurrentResult(), - ) + const result = observer.getOptimisticResult(defaultedOptions) React.useEffect(() => { - observer.setOptions(defaultedOptions) - }, [defaultedOptions, observer]) + errorResetBoundary.clearReset() + }, [errorResetBoundary]) - // Handle suspense - if (shouldSuspend(defaultedOptions, result)) { - throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary) - } + React.useEffect(() => { + observer.subscribe(notifyManager.batchCalls(() => {})) + }, [observer]) - // Handle error boundary - if ( - getHasError({ - result, - errorResetBoundary, - throwOnError: defaultedOptions.throwOnError, - query, - suspense: defaultedOptions.suspense, - }) - ) { - throw result.error + if (shouldSuspend(defaultedOptions, result, isRestoring)) { + return fetchOptimistic(defaultedOptions, observer, errorResetBoundary) } - ;(client.getDefaultOptions().queries as any)?._experimental_afterQuery?.( - defaultedOptions, - result, - ) - if ( - defaultedOptions.experimental_prefetchInRender && - !environmentManager.isServer() && - willFetch(result, isRestoring) + willFetch(result, isRestoring) && + defaultedOptions._optimisticResults !== 'optimistic' ) { - const promise = isNewCacheEntry - ? // Fetch immediately on render in order to ensure `.promise` is resolved even if the component is unmounted - fetchOptimistic(defaultedOptions, observer, errorResetBoundary) - : // subscribe to the "cache promise" so that we can finalize the currentThenable once data comes in - query?.promise - - promise?.catch(noop).finally(() => { - // `.updateResult()` will trigger `.#currentThenable` to finalize - observer.updateResult() - }) + observer.fetchOptimistic(defaultedOptions) } - // Handle result property usage tracking - return !defaultedOptions.notifyOnChangeProps - ? observer.trackResult(result) - : result + return result }