Skip to content

Commit 68d1adb

Browse files
committed
fix(query-core): reset isPlaceholderData on select-error results
1 parent 0fdf8db commit 68d1adb

3 files changed

Lines changed: 55 additions & 1 deletion

File tree

.changeset/clear-stale-select-error.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'@tanstack/query-core': patch
33
---
44

5-
fix(query-core): clear a stale `select` error when the observer switches to a query without data, so the previous query's select error no longer leaks into the new pending result
5+
fix(query-core): clear a stale `select` error when the observer switches to a query without data, and reset `isPlaceholderData` on select-error results to match the declared result types, so a previous query's select error no longer leaks into the new result

packages/query-core/src/__tests__/queryObserver.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,59 @@ describe('queryObserver', () => {
643643
})
644644
})
645645

646+
it('should not leak a stale select error through the memoized placeholderData path', async () => {
647+
const keyA = queryKey()
648+
const keyB = queryKey()
649+
const keyC = queryKey()
650+
const placeholder = { count: 0 }
651+
const observer = new QueryObserver(queryClient, {
652+
queryKey: keyA,
653+
queryFn: () => sleep(10).then(() => ({ count: 1 })),
654+
placeholderData: placeholder,
655+
select: (data) => ({ selected: data.count }),
656+
})
657+
const unsubscribe = observer.subscribe(() => {})
658+
await vi.advanceTimersByTimeAsync(10)
659+
expect(observer.getCurrentResult()).toMatchObject({
660+
status: 'success',
661+
data: { selected: 1 },
662+
})
663+
664+
observer.setOptions({
665+
queryKey: keyB,
666+
queryFn: () => sleep(10).then(() => ({ count: 2 })),
667+
placeholderData: placeholder,
668+
select: (): { selected: number } => {
669+
throw new Error('selector error')
670+
},
671+
})
672+
expect(observer.getCurrentResult()).toMatchObject({
673+
status: 'error',
674+
})
675+
676+
observer.setOptions({
677+
queryKey: keyC,
678+
queryFn: () => sleep(10).then(() => ({ count: 3 })),
679+
placeholderData: placeholder,
680+
select: (data) => ({ selected: data.count }),
681+
})
682+
expect(observer.getCurrentResult()).toMatchObject({
683+
status: 'success',
684+
data: { selected: 0 },
685+
error: null,
686+
isPlaceholderData: true,
687+
})
688+
689+
await vi.advanceTimersByTimeAsync(10)
690+
unsubscribe()
691+
692+
expect(observer.getCurrentResult()).toMatchObject({
693+
status: 'success',
694+
data: { selected: 3 },
695+
error: null,
696+
})
697+
})
698+
646699
it('should clear the select error when the query is reset', async () => {
647700
const key = queryKey()
648701
let shouldThrow = true

packages/query-core/src/queryObserver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ export class QueryObserver<
556556
data = this.#selectResult
557557
errorUpdatedAt = Date.now()
558558
status = 'error'
559+
isPlaceholderData = false
559560
}
560561

561562
const isFetching = newState.fetchStatus === 'fetching'

0 commit comments

Comments
 (0)