Skip to content

Commit fbd3488

Browse files
authored
feat: pass error as second parameter to retryDelay function (TanStack#1838)
this streamlines the interfaces of retry and retryDelay functions
1 parent f01ff6c commit fbd3488

File tree

6 files changed

+55
-12
lines changed

6 files changed

+55
-12
lines changed

docs/src/pages/reference/useMutation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ mutate(variables, {
6262
- If `false`, failed mutations will not retry by default.
6363
- If `true`, failed mutations will retry infinitely.
6464
- If set to an `number`, e.g. `3`, failed mutations will retry until the failed mutations count meets that number.
65-
- `retryDelay: (retryAttempt: number) => number`
66-
- This function receives a `retryAttempt` integer and returns the delay to apply before the next attempt in milliseconds.
65+
- `retryDelay: number | (retryAttempt: number, error: TError) => number`
66+
- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
6767
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
6868
- A function like `attempt => attempt * 1000` applies linear backoff.
6969
- `useErrorBoundary`

docs/src/pages/reference/useQuery.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ const result = useQuery({
8383
- If set to an `number`, e.g. `3`, failed queries will retry until the failed query count meets that number.
8484
- `retryOnMount: boolean`
8585
- If set to `false`, the query will not be retried on mount if it contains an error. Defaults to `true`.
86-
- `retryDelay: (retryAttempt: number) => number`
87-
- This function receives a `retryAttempt` integer and returns the delay to apply before the next attempt in milliseconds.
86+
- `retryDelay: number | (retryAttempt: number, error: TError) => number`
87+
- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
8888
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
8989
- A function like `attempt => attempt * 1000` applies linear backoff.
9090
- `staleTime: number | Infinity`

src/core/retryer.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { focusManager } from './focusManager'
22
import { onlineManager } from './onlineManager'
3-
import { functionalUpdate, sleep } from './utils'
3+
import { sleep } from './utils'
44

55
// TYPES
66

@@ -12,7 +12,7 @@ interface RetryerConfig<TData = unknown, TError = unknown> {
1212
onPause?: () => void
1313
onContinue?: () => void
1414
retry?: RetryValue<TError>
15-
retryDelay?: RetryDelayValue
15+
retryDelay?: RetryDelayValue<TError>
1616
}
1717

1818
export type RetryValue<TError> = boolean | number | ShouldRetryFunction<TError>
@@ -22,9 +22,12 @@ type ShouldRetryFunction<TError = unknown> = (
2222
error: TError
2323
) => boolean
2424

25-
export type RetryDelayValue = number | RetryDelayFunction
25+
export type RetryDelayValue<TError> = number | RetryDelayFunction<TError>
2626

27-
type RetryDelayFunction = (failureCount: number) => number
27+
type RetryDelayFunction<TError = unknown> = (
28+
failureCount: number,
29+
error: TError
30+
) => number
2831

2932
function defaultRetryDelay(failureCount: number) {
3033
return Math.min(1000 * 2 ** failureCount, 30000)
@@ -163,7 +166,10 @@ export class Retryer<TData = unknown, TError = unknown> {
163166
// Do we need to retry the request?
164167
const retry = config.retry ?? 3
165168
const retryDelay = config.retryDelay ?? defaultRetryDelay
166-
const delay = functionalUpdate(retryDelay, this.failureCount) || 0
169+
const delay =
170+
typeof retryDelay === 'function'
171+
? retryDelay(this.failureCount, error)
172+
: retryDelay
167173
const shouldRetry =
168174
retry === true ||
169175
(typeof retry === 'number' && this.failureCount < retry) ||

src/core/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export interface QueryOptions<
5252
* If set to a function `(failureCount, error) => boolean` failed queries will retry until the function returns false.
5353
*/
5454
retry?: RetryValue<TError>
55-
retryDelay?: RetryDelayValue
55+
retryDelay?: RetryDelayValue<TError>
5656
cacheTime?: number
5757
isDataEqual?: (oldData: TData | undefined, newData: TData) => boolean
5858
queryFn?: QueryFunction<TQueryFnData>
@@ -483,7 +483,7 @@ export interface MutationOptions<
483483
context: TContext | undefined
484484
) => Promise<void> | void
485485
retry?: RetryValue<TError>
486-
retryDelay?: RetryDelayValue
486+
retryDelay?: RetryDelayValue<TError>
487487
_defaulted?: boolean
488488
}
489489

src/react/tests/useQuery.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,6 +2779,43 @@ describe('useQuery', () => {
27792779
consoleMock.mockRestore()
27802780
})
27812781

2782+
it('should extract retryDelay from error', async () => {
2783+
const key = queryKey()
2784+
const consoleMock = mockConsoleError()
2785+
2786+
type DelayError = { delay: number }
2787+
2788+
const queryFn = jest.fn()
2789+
queryFn.mockImplementation(() => {
2790+
return Promise.reject({ delay: 50 })
2791+
})
2792+
2793+
function Page() {
2794+
const { status, failureCount } = useQuery(key, queryFn, {
2795+
retry: 1,
2796+
retryDelay: (_, error: DelayError) => error.delay,
2797+
})
2798+
2799+
return (
2800+
<div>
2801+
<h1>{status}</h1>
2802+
<h2>Failed {failureCount} times</h2>
2803+
</div>
2804+
)
2805+
}
2806+
2807+
const rendered = renderWithClient(queryClient, <Page />)
2808+
2809+
await sleep(10)
2810+
2811+
expect(queryFn).toHaveBeenCalledTimes(1)
2812+
2813+
await waitFor(() => rendered.getByText('Failed 2 times'))
2814+
2815+
expect(queryFn).toHaveBeenCalledTimes(2)
2816+
consoleMock.mockRestore()
2817+
})
2818+
27822819
// See https://github.com/tannerlinsley/react-query/issues/160
27832820
it('should continue retry after focus regain', async () => {
27842821
const key = queryKey()

src/react/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export interface UseMutationOptions<
7474
context: TContext | undefined
7575
) => Promise<void> | void
7676
retry?: RetryValue<TError>
77-
retryDelay?: RetryDelayValue
77+
retryDelay?: RetryDelayValue<TError>
7878
useErrorBoundary?: boolean
7979
}
8080

0 commit comments

Comments
 (0)