Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/pages/reference/useMutation.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ mutate(variables, {
- If `false`, failed mutations will not retry by default.
- If `true`, failed mutations will retry infinitely.
- If set to an `number`, e.g. `3`, failed mutations will retry until the failed mutations count meets that number.
- `retryDelay: (retryAttempt: number) => number`
- This function receives a `retryAttempt` integer and returns the delay to apply before the next attempt in milliseconds.
- `retryDelay: number | (retryAttempt: number, error: TError) => number`
- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
- A function like `attempt => attempt * 1000` applies linear backoff.
- `useErrorBoundary`
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ const result = useQuery({
- If set to an `number`, e.g. `3`, failed queries will retry until the failed query count meets that number.
- `retryOnMount: boolean`
- If set to `false`, the query will not be retried on mount if it contains an error. Defaults to `true`.
- `retryDelay: (retryAttempt: number) => number`
- This function receives a `retryAttempt` integer and returns the delay to apply before the next attempt in milliseconds.
- `retryDelay: number | (retryAttempt: number, error: TError) => number`
- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
- A function like `attempt => attempt * 1000` applies linear backoff.
- `staleTime: number | Infinity`
Expand Down
16 changes: 11 additions & 5 deletions src/core/retryer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { focusManager } from './focusManager'
import { onlineManager } from './onlineManager'
import { functionalUpdate, sleep } from './utils'
import { sleep } from './utils'

// TYPES

Expand All @@ -12,7 +12,7 @@ interface RetryerConfig<TData = unknown, TError = unknown> {
onPause?: () => void
onContinue?: () => void
retry?: RetryValue<TError>
retryDelay?: RetryDelayValue
retryDelay?: RetryDelayValue<TError>
}

export type RetryValue<TError> = boolean | number | ShouldRetryFunction<TError>
Expand All @@ -22,9 +22,12 @@ type ShouldRetryFunction<TError = unknown> = (
error: TError
) => boolean

export type RetryDelayValue = number | RetryDelayFunction
export type RetryDelayValue<TError> = number | RetryDelayFunction<TError>

type RetryDelayFunction = (failureCount: number) => number
type RetryDelayFunction<TError = unknown> = (
failureCount: number,
error: TError
) => number

function defaultRetryDelay(failureCount: number) {
return Math.min(1000 * 2 ** failureCount, 30000)
Expand Down Expand Up @@ -163,7 +166,10 @@ export class Retryer<TData = unknown, TError = unknown> {
// Do we need to retry the request?
const retry = config.retry ?? 3
const retryDelay = config.retryDelay ?? defaultRetryDelay
const delay = functionalUpdate(retryDelay, this.failureCount) || 0
const delay =
typeof retryDelay === 'function'
? retryDelay(this.failureCount, error)
: retryDelay
Comment on lines -166 to +172
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why || 0 was needed here before. On type level, the function was only allowed to return a number, and we also use the defaultRetryDelay in case retryDelay was undefined

const shouldRetry =
retry === true ||
(typeof retry === 'number' && this.failureCount < retry) ||
Expand Down
4 changes: 2 additions & 2 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface QueryOptions<
* If set to a function `(failureCount, error) => boolean` failed queries will retry until the function returns false.
*/
retry?: RetryValue<TError>
retryDelay?: RetryDelayValue
retryDelay?: RetryDelayValue<TError>
cacheTime?: number
isDataEqual?: (oldData: TData | undefined, newData: TData) => boolean
queryFn?: QueryFunction<TQueryFnData>
Expand Down Expand Up @@ -483,7 +483,7 @@ export interface MutationOptions<
context: TContext | undefined
) => Promise<void> | void
retry?: RetryValue<TError>
retryDelay?: RetryDelayValue
retryDelay?: RetryDelayValue<TError>
_defaulted?: boolean
}

Expand Down
37 changes: 37 additions & 0 deletions src/react/tests/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,43 @@ describe('useQuery', () => {
consoleMock.mockRestore()
})

it('should extract retryDelay from error', async () => {
const key = queryKey()
const consoleMock = mockConsoleError()

type DelayError = { delay: number }

const queryFn = jest.fn()
queryFn.mockImplementation(() => {
return Promise.reject({ delay: 50 })
})

function Page() {
const { status, failureCount } = useQuery(key, queryFn, {
retry: 1,
retryDelay: (_, error: DelayError) => error.delay,
})

return (
<div>
<h1>{status}</h1>
<h2>Failed {failureCount} times</h2>
</div>
)
}

const rendered = renderWithClient(queryClient, <Page />)

await sleep(10)

expect(queryFn).toHaveBeenCalledTimes(1)

await waitFor(() => rendered.getByText('Failed 2 times'))

expect(queryFn).toHaveBeenCalledTimes(2)
consoleMock.mockRestore()
})

// See https://github.com/tannerlinsley/react-query/issues/160
it('should continue retry after focus regain', async () => {
const key = queryKey()
Expand Down
2 changes: 1 addition & 1 deletion src/react/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface UseMutationOptions<
context: TContext | undefined
) => Promise<void> | void
retry?: RetryValue<TError>
retryDelay?: RetryDelayValue
retryDelay?: RetryDelayValue<TError>
useErrorBoundary?: boolean
}

Expand Down