Skip to content

Commit 90d8e81

Browse files
committed
fix: stop retry when observers unmount
1 parent 6e7de8f commit 90d8e81

File tree

3 files changed

+42
-5
lines changed

3 files changed

+42
-5
lines changed

src/core/query.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,12 @@ export class Query<TData = unknown, TError = unknown, TQueryFnData = TData> {
299299
if (!this.observers.length) {
300300
// If the transport layer does not support cancellation
301301
// we'll let the query continue so the result can be cached
302-
if (this.retryer && this.retryer.isTransportCancelable) {
303-
this.retryer.cancel()
302+
if (this.retryer) {
303+
if (this.retryer.isTransportCancelable) {
304+
this.retryer.cancel()
305+
} else {
306+
this.retryer.cancelRetry()
307+
}
304308
}
305309

306310
this.scheduleGc()

src/core/retryer.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function isCancelledError(value: any): value is CancelledError {
5858

5959
export class Retryer<TData = unknown, TError = unknown> {
6060
cancel: (options?: CancelOptions) => void
61+
cancelRetry: () => void
6162
continue: () => void
6263
failureCount: number
6364
isPaused: boolean
@@ -66,12 +67,16 @@ export class Retryer<TData = unknown, TError = unknown> {
6667
promise: Promise<TData>
6768

6869
constructor(config: RetryerConfig<TData, TError>) {
70+
let cancelRetry = false
6971
let cancelFn: ((options?: CancelOptions) => void) | undefined
7072
let continueFn: (() => void) | undefined
7173
let promiseResolve: (data: TData) => void
7274
let promiseReject: (error: TError) => void
7375

7476
this.cancel = cancelOptions => cancelFn?.(cancelOptions)
77+
this.cancelRetry = () => {
78+
cancelRetry = true
79+
}
7580
this.continue = () => continueFn?.()
7681
this.failureCount = 0
7782
this.isPaused = false
@@ -154,7 +159,7 @@ export class Retryer<TData = unknown, TError = unknown> {
154159
(typeof retry === 'number' && this.failureCount < retry) ||
155160
(typeof retry === 'function' && retry(this.failureCount, error))
156161

157-
if (!shouldRetry) {
162+
if (cancelRetry || !shouldRetry) {
158163
// We are done if the query does not need to be retried
159164
reject(error)
160165
return
@@ -173,8 +178,13 @@ export class Retryer<TData = unknown, TError = unknown> {
173178
return pause()
174179
}
175180
})
176-
// Try again
177-
.then(run)
181+
.then(() => {
182+
if (cancelRetry) {
183+
reject(error)
184+
} else {
185+
run()
186+
}
187+
})
178188
})
179189
}
180190

src/core/tests/queryCache.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,29 @@ describe('queryCache', () => {
676676
consoleMock.mockRestore()
677677
})
678678

679+
test('QueryObserver should stop retry when unsubscribing', async () => {
680+
const consoleMock = mockConsoleError()
681+
const key = queryKey()
682+
const testClient = new QueryClient()
683+
let count = 0
684+
const observer = new QueryObserver(testClient, {
685+
queryKey: key,
686+
queryFn: () => {
687+
count++
688+
return Promise.reject('reject')
689+
},
690+
retry: 10,
691+
retryDelay: 50,
692+
})
693+
const unsubscribe = observer.subscribe()
694+
await sleep(70)
695+
unsubscribe()
696+
await sleep(200)
697+
expect(count).toBe(2)
698+
testClient.clear()
699+
consoleMock.mockRestore()
700+
})
701+
679702
test('cancelQueries should revert queries to their previous state', async () => {
680703
const consoleMock = mockConsoleError()
681704
const key1 = queryKey()

0 commit comments

Comments
 (0)