Skip to content

Commit c7bf4aa

Browse files
committed
Merge remote-tracking branch 'react-query/v4' into 2927-offline-queries
# Conflicts: # src/core/retryer.ts
2 parents d8d893f + fb13a05 commit c7bf4aa

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed

src/core/query.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ export class Query<
361361
// Silently cancel current fetch if the user wants to cancel refetches
362362
this.cancel({ silent: true })
363363
} else if (this.promise) {
364+
// make sure that retries that were potentially cancelled due to unmounts can continue
365+
this.retryer?.continueRetry()
364366
// Return current promise if we are already fetching
365367
return this.promise
366368
}

src/core/retryer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function isCancelledError(value: any): value is CancelledError {
6060
export class Retryer<TData = unknown, TError = unknown> {
6161
cancel: (options?: CancelOptions) => void
6262
cancelRetry: () => void
63+
continueRetry: () => void
6364
continue: () => void
6465
failureCount: number
6566
isPaused: boolean
@@ -83,6 +84,10 @@ export class Retryer<TData = unknown, TError = unknown> {
8384
cancelRetry = true
8485
}
8586

87+
this.continueRetry = () => {
88+
cancelRetry = false
89+
}
90+
8691
const shouldPause = () =>
8792
!focusManager.isFocused() ||
8893
(config.networkMode !== 'always' && !onlineManager.isOnline())

src/reactjs/tests/useQuery.test.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,6 +2723,111 @@ describe('useQuery', () => {
27232723
consoleMock.mockRestore()
27242724
})
27252725

2726+
it('should continue retries when observers unmount and remount while waiting for a retry (#3031)', async () => {
2727+
const key = queryKey()
2728+
const consoleMock = mockConsoleError()
2729+
let count = 0
2730+
2731+
function Page() {
2732+
const result = useQuery(
2733+
key,
2734+
async () => {
2735+
count++
2736+
await sleep(10)
2737+
return Promise.reject('some error')
2738+
},
2739+
{
2740+
retry: 2,
2741+
retryDelay: 100,
2742+
}
2743+
)
2744+
2745+
return (
2746+
<div>
2747+
<div>error: {result.error ?? 'null'}</div>
2748+
<div>failureCount: {result.failureCount}</div>
2749+
</div>
2750+
)
2751+
}
2752+
2753+
function App() {
2754+
const [show, toggle] = React.useReducer(x => !x, true)
2755+
2756+
return (
2757+
<div>
2758+
<button onClick={toggle}>{show ? 'hide' : 'show'}</button>
2759+
{show && <Page />}
2760+
</div>
2761+
)
2762+
}
2763+
2764+
const rendered = renderWithClient(queryClient, <App />)
2765+
2766+
await waitFor(() => rendered.getByText('failureCount: 1'))
2767+
rendered.getByRole('button', { name: /hide/i }).click()
2768+
rendered.getByRole('button', { name: /show/i }).click()
2769+
await waitFor(() => rendered.getByText('error: some error'))
2770+
2771+
expect(count).toBe(3)
2772+
2773+
consoleMock.mockRestore()
2774+
})
2775+
2776+
it('should restart when observers unmount and remount while waiting for a retry when query was cancelled in between (#3031)', async () => {
2777+
const key = queryKey()
2778+
const consoleMock = mockConsoleError()
2779+
let count = 0
2780+
2781+
function Page() {
2782+
const result = useQuery(
2783+
key,
2784+
async () => {
2785+
count++
2786+
await sleep(10)
2787+
return Promise.reject('some error')
2788+
},
2789+
{
2790+
retry: 2,
2791+
retryDelay: 100,
2792+
}
2793+
)
2794+
2795+
return (
2796+
<div>
2797+
<div>error: {result.error ?? 'null'}</div>
2798+
<div>failureCount: {result.failureCount}</div>
2799+
</div>
2800+
)
2801+
}
2802+
2803+
function App() {
2804+
const [show, toggle] = React.useReducer(x => !x, true)
2805+
2806+
return (
2807+
<div>
2808+
<button onClick={toggle}>{show ? 'hide' : 'show'}</button>
2809+
<button onClick={() => queryClient.cancelQueries({ queryKey: key })}>
2810+
cancel
2811+
</button>
2812+
{show && <Page />}
2813+
</div>
2814+
)
2815+
}
2816+
2817+
const rendered = renderWithClient(queryClient, <App />)
2818+
2819+
await waitFor(() => rendered.getByText('failureCount: 1'))
2820+
rendered.getByRole('button', { name: /hide/i }).click()
2821+
rendered.getByRole('button', { name: /cancel/i }).click()
2822+
rendered.getByRole('button', { name: /show/i }).click()
2823+
await waitFor(() => rendered.getByText('error: some error'))
2824+
2825+
// initial fetch (1), which will be cancelled, followed by new mount(2) + 2 retries = 4
2826+
expect(count).toBe(4)
2827+
2828+
consoleMock.mockRestore()
2829+
})
2830+
27262831
it('should always fetch if refetchOnMount is set to always', async () => {
27272832
const key = queryKey()
27282833
const states: UseQueryResult<string>[] = []

0 commit comments

Comments
 (0)