-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
fix(query-core): correctly refetch resetQueries matches #10837
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 |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@tanstack/angular-query-experimental': patch | ||
| --- | ||
|
|
||
| Keep Angular's `whenStable()` pending when a query result signal is accessed before render. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,9 +60,41 @@ export function createBaseQuery< | |
| defaultedOptions._optimisticResults = isRestoring() | ||
| ? 'isRestoring' | ||
| : 'optimistic' | ||
|
|
||
| if (!isRestoring() && typeof defaultedOptions.queryFn === 'function') { | ||
| const originalQueryFn = defaultedOptions.queryFn | ||
|
|
||
| defaultedOptions.queryFn = (context) => { | ||
| const result = originalQueryFn(context) | ||
|
|
||
| if (result && typeof result.then === 'function') { | ||
| const complete = markPendingQueryFnTask() | ||
| void result.then( | ||
| complete, | ||
| complete, | ||
| ) | ||
|
Comment on lines
+67
to
+75
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
set -euo pipefail
echo "Verify query function contexts expose AbortSignal:"
rg -n -C2 'signal:\s*AbortSignal' packages/query-core/src
echo
echo "Inspect the wrapped queryFn and PendingTask cleanup paths:"
sed -n '64,96p' packages/angular-query-experimental/src/create-base-query.ts
sed -n '183,193p' packages/angular-query-experimental/src/create-base-query.tsRepository: TanStack/query Length of output: 3458 Complete queryFn PendingTask on
Suggested fix defaultedOptions.queryFn = (context) => {
const result = originalQueryFn(context)
if (result && typeof result.then === 'function') {
const complete = markPendingQueryFnTask()
+ const onAbort = () => {
+ complete()
+ }
+
+ context.signal.addEventListener('abort', onAbort, { once: true })
void result.then(
- complete,
- complete,
+ () => {
+ context.signal.removeEventListener('abort', onAbort)
+ complete()
+ },
+ () => {
+ context.signal.removeEventListener('abort', onAbort)
+ complete()
+ },
)
}
return result
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| return result | ||
| } | ||
| } | ||
|
|
||
| return defaultedOptions | ||
| }) | ||
|
|
||
| const pendingTaskRefsFromQueryFn = new Set<PendingTaskRef>() | ||
|
|
||
| const markPendingQueryFnTask = () => { | ||
| const pendingTaskRef = pendingTasks.add() | ||
| const done = () => { | ||
| if (pendingTaskRefsFromQueryFn.delete(done)) { | ||
| pendingTaskRef() | ||
| } | ||
| } | ||
| pendingTaskRefsFromQueryFn.add(done) | ||
| return done | ||
| } | ||
|
|
||
| const observerSignal = (() => { | ||
| let instance: QueryObserver< | ||
| TQueryFnData, | ||
|
|
@@ -110,37 +142,41 @@ export function createBaseQuery< | |
| const observer = observerSignal() | ||
| let pendingTaskRef: PendingTaskRef | null = null | ||
|
|
||
| const updateState = (state: QueryObserverResult<TData, TError>) => { | ||
| ngZone.run(() => { | ||
| if (state.fetchStatus === 'fetching' && !pendingTaskRef) { | ||
| pendingTaskRef = pendingTasks.add() | ||
| } | ||
|
|
||
| if (state.fetchStatus === 'idle' && pendingTaskRef) { | ||
| pendingTaskRef() | ||
| pendingTaskRef = null | ||
| } | ||
|
|
||
| if ( | ||
| state.isError && | ||
| !state.isFetching && | ||
| shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery(), | ||
| ]) | ||
| ) { | ||
| ngZone.onError.emit(state.error) | ||
| throw state.error | ||
| } | ||
| resultFromSubscriberSignal.set(state) | ||
| }) | ||
| } | ||
|
|
||
| const unsubscribe = isRestoring() | ||
| ? () => undefined | ||
| : untracked(() => | ||
| ngZone.runOutsideAngular(() => { | ||
| return observer.subscribe( | ||
| notifyManager.batchCalls((state) => { | ||
| ngZone.run(() => { | ||
| if (state.fetchStatus === 'fetching' && !pendingTaskRef) { | ||
| pendingTaskRef = pendingTasks.add() | ||
| } | ||
|
|
||
| if (state.fetchStatus === 'idle' && pendingTaskRef) { | ||
| pendingTaskRef() | ||
| pendingTaskRef = null | ||
| } | ||
|
|
||
| if ( | ||
| state.isError && | ||
| !state.isFetching && | ||
| shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery(), | ||
| ]) | ||
| ) { | ||
| ngZone.onError.emit(state.error) | ||
| throw state.error | ||
| } | ||
| resultFromSubscriberSignal.set(state) | ||
| }) | ||
| }), | ||
| const unsubscribeObserver = observer.subscribe( | ||
| notifyManager.batchCalls(updateState), | ||
| ) | ||
|
|
||
| return unsubscribeObserver | ||
| }), | ||
| ) | ||
|
|
||
|
|
@@ -149,6 +185,10 @@ export function createBaseQuery< | |
| pendingTaskRef() | ||
| pendingTaskRef = null | ||
| } | ||
| for (const pendingTaskRefFromQueryFn of pendingTaskRefsFromQueryFn) { | ||
| pendingTaskRefFromQueryFn() | ||
| } | ||
| pendingTaskRefsFromQueryFn.clear() | ||
| unsubscribe() | ||
| }) | ||
| }) | ||
|
|
||
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.
Assert instability before destroy so this can't pass as a false positive.
As written, this still passes if
whenStable()resolves immediately afterdetectChanges()and destroy cleanup never does anything. Check that the promise is still pending beforefixture.destroy(), then assert it resolves after destroy.Suggested test tightening
const app = TestBed.inject(ApplicationRef) const fixture = TestBed.createComponent(NeverResolvesComponent) fixture.detectChanges() - const stableResult = app.whenStable().then(() => true) + let stableResolved = false + const stableResult = app.whenStable().then(() => { + stableResolved = true + return true + }) + + await Promise.resolve() + expect(stableResolved).toBe(false) fixture.destroy() const timeoutResult = new Promise<boolean>((resolve) => { setTimeout(() => {📝 Committable suggestion
🤖 Prompt for AI Agents