Skip to content

Commit e4f5ce8

Browse files
authored
fix(core): never revert to undefined data (#9577)
* fix(core): never revert to undefined data * fix: throw CancelledError for imperative fetches, but keep query reverted to idle state * chore: improve test
1 parent 2989e69 commit e4f5ce8

File tree

2 files changed

+44
-39
lines changed

2 files changed

+44
-39
lines changed

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

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
22
import { queryKey, sleep } from '@tanstack/query-test-utils'
33
import {
4+
CancelledError,
45
MutationObserver,
56
QueryClient,
67
QueryObserver,
@@ -993,54 +994,25 @@ describe('queryClient', () => {
993994
describe('cancelQueries', () => {
994995
test('should revert queries to their previous state', async () => {
995996
const key1 = queryKey()
996-
const key2 = queryKey()
997-
const key3 = queryKey()
998-
await queryClient.fetchQuery({
999-
queryKey: key1,
1000-
queryFn: () => 'data',
1001-
})
1002-
try {
1003-
await queryClient.fetchQuery({
1004-
queryKey: key2,
1005-
queryFn: async () => {
1006-
return Promise.reject<unknown>('err')
1007-
},
1008-
})
1009-
} catch {}
1010-
queryClient.fetchQuery({
997+
queryClient.setQueryData(key1, 'data')
998+
999+
const pending = queryClient.fetchQuery({
10111000
queryKey: key1,
10121001
queryFn: () => sleep(1000).then(() => 'data2'),
10131002
})
1014-
try {
1015-
queryClient.fetchQuery({
1016-
queryKey: key2,
1017-
queryFn: () =>
1018-
sleep(1000).then(() => Promise.reject<unknown>('err2')),
1019-
})
1020-
} catch {}
1021-
queryClient.fetchQuery({
1022-
queryKey: key3,
1023-
queryFn: () => sleep(1000).then(() => 'data3'),
1024-
})
1003+
10251004
await vi.advanceTimersByTimeAsync(10)
1005+
10261006
await queryClient.cancelQueries()
1007+
1008+
// with previous data present, imperative fetch should resolve to that data after cancel
1009+
await expect(pending).resolves.toBe('data')
1010+
10271011
const state1 = queryClient.getQueryState(key1)
1028-
const state2 = queryClient.getQueryState(key2)
1029-
const state3 = queryClient.getQueryState(key3)
10301012
expect(state1).toMatchObject({
10311013
data: 'data',
10321014
status: 'success',
10331015
})
1034-
expect(state2).toMatchObject({
1035-
data: undefined,
1036-
error: 'err',
1037-
status: 'error',
1038-
})
1039-
expect(state3).toMatchObject({
1040-
data: undefined,
1041-
status: 'pending',
1042-
fetchStatus: 'idle',
1043-
})
10441016
})
10451017

10461018
test('should not revert if revert option is set to false', async () => {
@@ -1060,6 +1032,34 @@ describe('queryClient', () => {
10601032
status: 'error',
10611033
})
10621034
})
1035+
1036+
test('should throw CancelledError for imperative methods when initial fetch is cancelled', async () => {
1037+
const key = queryKey()
1038+
1039+
const promise = queryClient.fetchQuery({
1040+
queryKey: key,
1041+
queryFn: async () => {
1042+
await sleep(50)
1043+
return 25
1044+
},
1045+
})
1046+
1047+
await vi.advanceTimersByTimeAsync(10)
1048+
1049+
await queryClient.cancelQueries({ queryKey: key })
1050+
1051+
// we have to reject here because we can't resolve with `undefined`
1052+
// the alternative would be a never-ending promise
1053+
await expect(promise).rejects.toBeInstanceOf(CancelledError)
1054+
1055+
// however, the query was correctly reverted to pending state
1056+
expect(queryClient.getQueryState(key)).toMatchObject({
1057+
status: 'pending',
1058+
fetchStatus: 'idle',
1059+
data: undefined,
1060+
error: null,
1061+
})
1062+
})
10631063
})
10641064

10651065
describe('refetchQueries', () => {

packages/query-core/src/query.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,12 @@ export class Query<
558558
fetchStatus: 'idle' as const,
559559
})
560560
// transform error into reverted state data
561-
return this.state.data!
561+
// if the initial fetch was cancelled, we have no data, so we have
562+
// to get reject with a CancelledError
563+
if (this.state.data === undefined) {
564+
throw error
565+
}
566+
return this.state.data
562567
}
563568
}
564569
this.#dispatch({

0 commit comments

Comments
 (0)