Skip to content

Commit 765740d

Browse files
committed
feat: revert to previous state on cancellation
1 parent 7d65bef commit 765740d

File tree

4 files changed

+100
-14
lines changed

4 files changed

+100
-14
lines changed

src/core/query.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CancelOptions,
23
CancelledError,
34
Updater,
45
defaultRetryDelay,
@@ -116,7 +117,7 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
116117
private cache: QueryCache
117118
private promise?: Promise<TData>
118119
private gcTimeout?: number
119-
private cancelFetch?: (silent?: boolean) => void
120+
private cancelFetch?: (options?: CancelOptions) => void
120121
private continueFetch?: () => void
121122
private isTransportCancelable?: boolean
122123
private observers: QueryObserver<any, any, any, any>[]
@@ -175,9 +176,9 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
175176
}, this.cacheTime)
176177
}
177178

178-
cancel(silent?: boolean): Promise<void> {
179+
cancel(options?: CancelOptions): Promise<void> {
179180
const promise: Promise<any> = this.promise || Promise.resolve()
180-
this.cancelFetch?.(silent)
181+
this.cancelFetch?.(options)
181182
return promise.then(noop).catch(noop)
182183
}
183184

@@ -338,7 +339,7 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
338339
if (this.state.isFetching)
339340
if (fetchOptions?.fetchMore && this.state.updatedAt) {
340341
// Silently cancel current fetch if the user wants to fetch more
341-
this.cancel(true)
342+
this.cancel({ silent: true })
342343
} else if (this.promise) {
343344
// Return current promise if we are already fetching
344345
return this.promise
@@ -737,9 +738,22 @@ export function queryReducer<TData, TError>(
737738
updatedAt: action.updatedAt ?? Date.now(),
738739
}
739740
case 'error':
741+
const error = action.error as unknown
742+
743+
if (isCancelledError(error) && error.revert) {
744+
return {
745+
...state,
746+
failureCount: 0,
747+
isFetching: false,
748+
isFetchingNextPage: false,
749+
isFetchingPreviousPage: false,
750+
status: state.error ? 'error' : state.updatedAt ? 'success' : 'idle',
751+
}
752+
}
753+
740754
return {
741755
...state,
742-
error: action.error,
756+
error: error as TError,
743757
errorUpdateCount: state.errorUpdateCount + 1,
744758
failureCount: state.failureCount + 1,
745759
isFetching: false,

src/core/queryClient.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CancelOptions,
23
QueryFilters,
34
Updater,
45
isDocumentVisible,
@@ -128,16 +129,29 @@ export class QueryClient {
128129
})
129130
}
130131

131-
cancelQueries(filters?: QueryFilters): Promise<void>
132-
cancelQueries(queryKey?: QueryKey, filters?: QueryFilters): Promise<void>
132+
cancelQueries(filters?: QueryFilters, options?: CancelOptions): Promise<void>
133+
cancelQueries(
134+
queryKey?: QueryKey,
135+
filters?: QueryFilters,
136+
options?: CancelOptions
137+
): Promise<void>
133138
cancelQueries(
134139
arg1?: QueryKey | QueryFilters,
135-
arg2?: QueryFilters
140+
arg2?: QueryFilters | CancelOptions,
141+
arg3?: CancelOptions
136142
): Promise<void> {
137-
const promises = notifyManager.batch(() =>
138-
this.cache.findAll(arg1, arg2).map(query => query.cancel())
139-
)
140-
return Promise.all(promises).then(noop)
143+
const [filters, options] = parseFilterArgs(arg1, arg2, arg3)
144+
const cancelOptions = options || {}
145+
146+
if (typeof cancelOptions.revert === 'undefined') {
147+
cancelOptions.revert = true
148+
}
149+
150+
notifyManager.batch(() => {
151+
this.cache.findAll(filters).map(query => query.cancel(cancelOptions))
152+
})
153+
154+
return Promise.resolve().then(noop)
141155
}
142156

143157
invalidateQueries(

src/core/tests/queryCache.test.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,57 @@ describe('queryCache', () => {
671671
consoleMock.mockRestore()
672672
})
673673

674+
test('cancelQueries should revert queries to their previous state', async () => {
675+
const consoleMock = mockConsoleError()
676+
const key1 = queryKey()
677+
const key2 = queryKey()
678+
const key3 = queryKey()
679+
const testCache = new QueryCache()
680+
const testClient = new QueryClient({ cache: testCache })
681+
await testClient.fetchQueryData(key1, async () => {
682+
return 'data'
683+
})
684+
try {
685+
await testClient.fetchQueryData(key2, async () => {
686+
return Promise.reject('err')
687+
})
688+
} catch {}
689+
testClient.fetchQueryData(key1, async () => {
690+
await sleep(1000)
691+
return 'data2'
692+
})
693+
try {
694+
testClient.fetchQueryData(key2, async () => {
695+
await sleep(1000)
696+
return Promise.reject('err2')
697+
})
698+
} catch {}
699+
testClient.fetchQueryData(key3, async () => {
700+
await sleep(1000)
701+
return 'data3'
702+
})
703+
await sleep(10)
704+
await testClient.cancelQueries()
705+
const state1 = testClient.getQueryState(key1)
706+
const state2 = testClient.getQueryState(key2)
707+
const state3 = testClient.getQueryState(key3)
708+
testCache.clear()
709+
expect(state1).toMatchObject({
710+
data: 'data',
711+
status: 'success',
712+
})
713+
expect(state2).toMatchObject({
714+
data: undefined,
715+
error: 'err',
716+
status: 'error',
717+
})
718+
expect(state3).toMatchObject({
719+
data: undefined,
720+
status: 'idle',
721+
})
722+
consoleMock.mockRestore()
723+
})
724+
674725
test('refetchQueries should refetch all queries when no arguments are given', async () => {
675726
const key1 = queryKey()
676727
const key2 = queryKey()

src/core/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,17 @@ interface Cancelable {
5454
cancel(): void
5555
}
5656

57+
export interface CancelOptions {
58+
revert?: boolean
59+
silent?: boolean
60+
}
61+
5762
export class CancelledError {
63+
revert?: boolean
5864
silent?: boolean
59-
constructor(silent?: boolean) {
60-
this.silent = silent
65+
constructor(options?: CancelOptions) {
66+
this.revert = options?.revert
67+
this.silent = options?.silent
6168
}
6269
}
6370

0 commit comments

Comments
 (0)