-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
build(react-query): update queryerrorresetboundary to use a more robust reset mechanism with reset id #10375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -96,75 +96,28 @@ export function useBaseQuery< | |||||||||||
| ), | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| // note: this must be called before useSyncExternalStore | ||||||||||||
| const result = observer.getOptimisticResult(defaultedOptions) | ||||||||||||
| observer.setOptions(defaultedOptions, { isNewCacheEntry }) | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Description: Verify setOptions signature in query-core
echo "=== setOptions method signature ==="
ast-grep --pattern 'setOptions($PARAM) {
$$$
}'Repository: TanStack/query Length of output: 93 π Script executed: # Find the files mentioned
fd -type f -name "useBaseQuery.ts"
fd -type f -name "queryObserver.ts"Repository: TanStack/query Length of output: 399 π Script executed: # Find the files mentioned
fd "useBaseQuery.ts"
fd "queryObserver.ts"Repository: TanStack/query Length of output: 310 π Script executed: # Read useBaseQuery.ts from react-query around line 99
wc -l packages/react-query/src/useBaseQuery.ts
head -110 packages/react-query/src/useBaseQuery.ts | tail -20Repository: TanStack/query Length of output: 597 π Script executed: # Read queryObserver.ts around lines 138-176 to see setOptions signature
wc -l packages/query-core/src/queryObserver.ts
sed -n '130,180p' packages/query-core/src/queryObserver.tsRepository: TanStack/query Length of output: 1345 π Script executed: # Check lines 87-89 in useBaseQuery.ts to see the "dead code" mentioned
sed -n '85,92p' packages/react-query/src/useBaseQuery.tsRepository: TanStack/query Length of output: 295 π Script executed: # Search for all setOptions definitions to check for overloads or multiple implementations
rg "setOptions\s*\(" packages/query-core/src/queryObserver.tsRepository: TanStack/query Length of output: 148 π Script executed: # Check if isNewCacheEntry is used anywhere else in the file
rg "isNewCacheEntry" packages/react-query/src/useBaseQuery.ts -A 2 -B 2Repository: TanStack/query Length of output: 381 π Script executed: # Check if Observer class extends from QueryObserver or if there are other setOptions definitions
rg "class.*Observer" packages/query-core/src/queryObserver.ts -A 3Repository: TanStack/query Length of output: 160 π Script executed: # Check what Observer is imported as in useBaseQuery.ts
head -30 packages/react-query/src/useBaseQuery.ts | grep -E "^import|^export|Observer"Repository: TanStack/query Length of output: 501 π Script executed: # Verify the exact line numbers for context around the setOptions call
sed -n '85,105p' packages/react-query/src/useBaseQuery.ts | cat -nRepository: TanStack/query Length of output: 786 Remove second parameter from
Either:
π€ Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| 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]) | ||||||||||||
|
Comment on lines
103
to
+105
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Effect dependency may cause excessive The effect runs whenever This conflicts with the stated PR objective: if Consider whether this effect should only run under specific conditions (e.g., after a successful query render). π€ Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| // Handle suspense | ||||||||||||
| if (shouldSuspend(defaultedOptions, result)) { | ||||||||||||
| throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary) | ||||||||||||
| } | ||||||||||||
| React.useEffect(() => { | ||||||||||||
| observer.subscribe(notifyManager.batchCalls(() => {})) | ||||||||||||
| }, [observer]) | ||||||||||||
|
Comment on lines
+107
to
+109
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Empty subscription callback prevents component re-renders. The subscription callback is empty ( The previous implementation likely used π Suggested fix pattern+ const [, forceUpdate] = React.useReducer((x) => x + 1, 0)
+
React.useEffect(() => {
- observer.subscribe(notifyManager.batchCalls(() => {}))
+ const unsubscribe = observer.subscribe(
+ notifyManager.batchCalls(forceUpdate)
+ )
+ return unsubscribe
}, [observer])Also note: the effect is missing a cleanup function to unsubscribe when the component unmounts or observer changes. π€ Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| // 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) | ||||||||||||
|
Comment on lines
+111
to
+112
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Description: Verify shouldSuspend signature
echo "=== shouldSuspend function signature ==="
rg -n -A 5 'export const shouldSuspend' packages/react-query/src/suspense.tsRepository: TanStack/query Length of output: 338 π Script executed: # Find useBaseQuery.ts and read the relevant lines
cd packages/react-query/src && cat -n useBaseQuery.ts | sed -n '100,125p'Repository: TanStack/query Length of output: 804 π Script executed: # Find fetchOptimistic function definition
rg -n -B 2 -A 10 'export.*fetchOptimistic|const fetchOptimistic' packages/react-query/src/Repository: TanStack/query Length of output: 837 π Script executed: # Check the return type of useBaseQuery hook
rg -n 'function useBaseQuery|const useBaseQuery' -A 3 packages/react-query/src/useBaseQuery.tsRepository: TanStack/query Length of output: 134 π Script executed: # Get complete fetchOptimistic function to see return type
rg -n -A 25 'export const fetchOptimistic = ' packages/react-query/src/suspense.tsRepository: TanStack/query Length of output: 595 π Script executed: # Get useBaseQuery full signature with return type
rg -n -A 15 'export function useBaseQuery' packages/react-query/src/useBaseQuery.tsRepository: TanStack/query Length of output: 388 π Script executed: # Get useBaseQuery return type (should follow the parameter list)
cat -n packages/react-query/src/useBaseQuery.ts | sed -n '27,50p'Repository: TanStack/query Length of output: 997 Arity and return type mismatches prevent this code from compiling. Line 111-112 contains two critical issues:
Additionally, for React Suspense to work correctly, the Promise must be thrown, not returned. Fix- if (shouldSuspend(defaultedOptions, result, isRestoring)) {
- return fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
+ if (shouldSuspend(defaultedOptions, result) && !isRestoring) {
+ throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
}The π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ;(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 | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
Repository: TanStack/query
Length of output: 854
π Script executed:
Repository: TanStack/query
Length of output: 1296
π Script executed:
Repository: TanStack/query
Length of output: 2682
Critical: Interface change breaks consumers in
errorBoundaryUtils.ts.The interface no longer exposes
isReset()but onlygetResetId(). However,errorBoundaryUtils.tsstill callserrorResetBoundary.isReset()at lines 41 and 76, and the same issue exists inpreact-query/src/errorBoundaryUtils.tsat lines 35 and 70. This will cause a runtime error:Update the consumer code to use the new
getResetId()API with appropriate comparison logic, or keepisReset()in the interface alongsidegetResetId()for backward compatibility.