Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding in a function for checking if retries should be performed #294

Merged
merged 4 commits into from Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -792,13 +792,14 @@ function Todos() {

## Retries

When a `useQuery` query fails (the function throws an error), React Query will automatically retry the query if that query's request has not reached the max number of consecutive retries (defaults to `3`).
When a `useQuery` query fails (the function throws an error), React Query will automatically retry the query if that query's request has not reached the max number of consecutive retries (defaults to `3`) and the retryChecker function returns true (defaults to `(error) => true`).

You can configure retries both on a global level and an individual query level.

- Setting `retry = false` will disable retries.
- Setting `retry = 6` will retry failing requests 6 times before showing the final error thrown by the function.
- Setting `retry = true` will infinitely retry failing requests.
- Setting `retryChecker = (error) => ...` allows to stop retries based on error type (e.g. many HTTP 4XX errors shouldn't be retried)

```js
import { useQuery } from 'react-query'
Expand All @@ -809,6 +810,15 @@ const { status, data, error } = useQuery(['todos', 1], fetchTodoListPage, {
})
```

```js
import { useQuery } from 'react-query'

// Make specific query no retry if the error thrown is NoRetry
const { status, data, error } = useQuery(['todos', 1], fetchTodoListPage, {
retryChecker: error => error !== 'NoRetry',
})
```

## Retry Delay

By default, retries in React Query do not happen immediately after a request fails. As is standard, a back-off delay is gradually applied to each retry attempt.
Expand Down
1 change: 1 addition & 0 deletions src/config.js
Expand Up @@ -7,6 +7,7 @@ export const defaultConfigRef = {
current: {
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
retryChecker: error => true,
staleTime: 0,
cacheTime: 5 * 60 * 1000,
refetchAllOnWindowFocus: true,
Expand Down
7 changes: 4 additions & 3 deletions src/queryCache.js
Expand Up @@ -313,10 +313,11 @@ export function makeQueryCache() {

// Do we need to retry the request?
if (
// Only retry if the document is visible
query.config.retry === true ||
query.state.failureCount <= query.config.retry
query.config.retryChecker(error, query.state.failureCount) &&
(query.config.retry === true ||
query.state.failureCount <= query.config.retry)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Reverse this order so that the checker isn't even called if it's not true or less than the retry count?

) {
// Only retry if the document is visible
if (!isDocumentVisible()) {
// set this flag to continue fetch retries on focus
query.shouldContinueRetryOnFocus = true
Expand Down
2 changes: 1 addition & 1 deletion src/tests/useInfiniteQuery.test.js
Expand Up @@ -11,7 +11,7 @@ import { sleep } from './utils'

const pageSize = 10

const initialItems = (page) => {
const initialItems = page => {
return {
items: [...new Array(10)].fill(null).map((_, d) => page * pageSize + d),
nextId: page + 1,
Expand Down
31 changes: 31 additions & 0 deletions src/tests/useQuery.test.js
Expand Up @@ -249,6 +249,37 @@ describe('useQuery', () => {
expect(queryFn).toHaveBeenCalledTimes(2)
})

it('should not retry if retryChecker returns `false`', async () => {
const queryFn = jest.fn()

queryFn.mockImplementation(() => {
return Promise.reject('NoRetry')
})

function Page() {
const { status, failureCount } = useQuery('test', queryFn, {
retryDelay: 1,
retryChecker: error => error !== 'NoRetry',
})

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

const rendered = render(<Page />)

await waitForElement(() => rendered.getByText('loading'))
await waitForElement(() => rendered.getByText('error'))

await waitForElement(() => rendered.getByText('Failed 1 times'))

expect(queryFn).toHaveBeenCalledTimes(1)
})

it('should garbage collect queries without data immediately', async () => {
function Page() {
const [filter, setFilter] = React.useState('')
Expand Down