Skip to content

Commit 7250804

Browse files
committed
fix(query-core): remove focus check from retryer for refetchIntervallnBackground
1 parent de3626a commit 7250804

File tree

4 files changed

+94
-99
lines changed

4 files changed

+94
-99
lines changed

packages/query-core/src/__tests__/query.test.tsx

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('query', () => {
5858
expect(query.gcTime).toBe(200)
5959
})
6060

61-
it('should continue retry after focus regain and resolve all promises', async () => {
61+
it('should continue retry and resolve even with focus state changes', async () => {
6262
const key = queryKey()
6363

6464
// make page unfocused
@@ -86,23 +86,16 @@ describe('query', () => {
8686
result = data
8787
})
8888

89-
// Check if we do not have a result
89+
// Check if we do not have a result initially
9090
expect(result).toBeUndefined()
9191

92-
// Check if the query is really paused
93-
await vi.advanceTimersByTimeAsync(50)
94-
expect(result).toBeUndefined()
92+
// With new behavior, retries continue in background
93+
// Wait for retries to complete
94+
await vi.advanceTimersByTimeAsync(10)
95+
expect(result).toBe('data3')
96+
expect(count).toBe(3)
9597

96-
// Reset visibilityState to original value
9798
visibilityMock.mockRestore()
98-
window.dispatchEvent(new Event('visibilitychange'))
99-
100-
// There should not be a result yet
101-
expect(result).toBeUndefined()
102-
103-
// By now we should have a value
104-
await vi.advanceTimersByTimeAsync(50)
105-
expect(result).toBe('data3')
10699
})
107100

108101
it('should continue retry after reconnect and resolve all promises', async () => {
@@ -153,11 +146,39 @@ describe('query', () => {
153146
onlineMock.mockRestore()
154147
})
155148

156-
it('should throw a CancelledError when a paused query is cancelled', async () => {
149+
it('should continue retry in background when page is not focused', async () => {
157150
const key = queryKey()
158-
159151
// make page unfocused
160152
const visibilityMock = mockVisibilityState('hidden')
153+
let count = 0
154+
let result
155+
const promise = queryClient.fetchQuery({
156+
queryKey: key,
157+
queryFn: () => {
158+
count++
159+
if (count === 3) {
160+
return `data${count}`
161+
}
162+
throw new Error(`error${count}`)
163+
},
164+
retry: 3,
165+
retryDelay: 1,
166+
})
167+
promise.then((data) => {
168+
result = data
169+
})
170+
// Check if we do not have a result initially
171+
expect(result).toBeUndefined()
172+
// Unlike the old behavior, retry should continue in background
173+
// Wait for retries to complete
174+
await vi.advanceTimersByTimeAsync(10)
175+
expect(result).toBe('data3')
176+
expect(count).toBe(3)
177+
visibilityMock.mockRestore()
178+
})
179+
180+
it('should throw a CancelledError when a retrying query is cancelled', async () => {
181+
const key = queryKey()
161182

162183
let count = 0
163184
let result: unknown
@@ -169,7 +190,7 @@ describe('query', () => {
169190
throw new Error(`error${count}`)
170191
},
171192
retry: 3,
172-
retryDelay: 1,
193+
retryDelay: 100, // Longer delay to allow cancellation
173194
})
174195

175196
promise.catch((data) => {
@@ -178,22 +199,20 @@ describe('query', () => {
178199

179200
const query = queryCache.find({ queryKey: key })!
180201

181-
// Check if the query is really paused
182-
await vi.advanceTimersByTimeAsync(50)
202+
// Wait briefly for first failure and start of retry
203+
await vi.advanceTimersByTimeAsync(1)
183204
expect(result).toBeUndefined()
184205

185-
// Cancel query
206+
// Cancel query during retry
186207
query.cancel()
187208

188209
// Check if the error is set to the cancelled error
189210
try {
190211
await promise
191212
expect.unreachable()
192213
} catch {
193-
expect(result).toBeInstanceOf(CancelledError)
194-
} finally {
195-
// Reset visibilityState to original value
196-
visibilityMock.mockRestore()
214+
expect(result instanceof CancelledError).toBe(true)
215+
expect(result instanceof Error).toBe(true)
197216
}
198217
})
199218

@@ -236,6 +255,43 @@ describe('query', () => {
236255
expect(queryCache.find({ queryKey: key })?.state.data).toBe('data')
237256
})
238257

258+
it('should continue refetchInterval with retries in background when tab is inactive', async () => {
259+
const key = queryKey()
260+
const visibilityMock = mockVisibilityState('hidden')
261+
262+
let totalRequests = 0
263+
264+
const queryObserver = new QueryObserver(queryClient, {
265+
queryKey: key,
266+
queryFn: () => {
267+
totalRequests++
268+
// Always fail to simulate network offline
269+
throw new Error(`Network error ${totalRequests}`)
270+
},
271+
refetchInterval: 60000,
272+
refetchIntervalInBackground: true,
273+
retry: 3,
274+
retryDelay: 1,
275+
})
276+
277+
queryObserver.subscribe(() => {})
278+
279+
// First interval: t=0 to t=60s (initial query + retries)
280+
await vi.advanceTimersByTimeAsync(60000)
281+
expect(totalRequests).toBe(4)
282+
283+
// Second interval: t=60s to t=120s (refetch + retries)
284+
await vi.advanceTimersByTimeAsync(60000)
285+
expect(totalRequests).toBe(8)
286+
287+
// Third interval: t=120s to t=180s (refetch + retries)
288+
await vi.advanceTimersByTimeAsync(60000)
289+
expect(totalRequests).toBe(12)
290+
291+
queryObserver.destroy()
292+
visibilityMock.mockRestore()
293+
})
294+
239295
test('should provide context to queryFn', () => {
240296
const key = queryKey()
241297

packages/query-core/src/retryer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { focusManager } from './focusManager'
21
import { onlineManager } from './onlineManager'
32
import { pendingThenable } from './thenable'
43
import { isServer, sleep } from './utils'
@@ -96,7 +95,6 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
9695
}
9796

9897
const canContinue = () =>
99-
focusManager.isFocused() &&
10098
(config.networkMode === 'always' || onlineManager.isOnline()) &&
10199
config.canRun()
102100

packages/react-query/src/__tests__/useQuery.test.tsx

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3431,7 +3431,7 @@ describe('useQuery', () => {
34313431
})
34323432

34333433
// See https://github.com/tannerlinsley/react-query/issues/160
3434-
it('should continue retry after focus regain', async () => {
3434+
it('should continue retry in background even when page is not focused', async () => {
34353435
const key = queryKey()
34363436

34373437
// make page unfocused
@@ -3462,29 +3462,8 @@ describe('useQuery', () => {
34623462

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

3465-
// The query should display the first error result
3466-
await vi.advanceTimersByTimeAsync(0)
3467-
expect(rendered.getByText('failureCount 1')).toBeInTheDocument()
3468-
expect(
3469-
rendered.getByText('failureReason fetching error 1'),
3470-
).toBeInTheDocument()
3471-
expect(rendered.getByText('status pending')).toBeInTheDocument()
3472-
expect(rendered.getByText('error null')).toBeInTheDocument()
3473-
3474-
// Check if the query really paused
3475-
await vi.advanceTimersByTimeAsync(0)
3476-
expect(rendered.getByText('failureCount 1')).toBeInTheDocument()
3477-
expect(
3478-
rendered.getByText('failureReason fetching error 1'),
3479-
).toBeInTheDocument()
3480-
3481-
act(() => {
3482-
// reset visibilityState to original value
3483-
visibilityMock.mockRestore()
3484-
window.dispatchEvent(new Event('visibilitychange'))
3485-
})
3486-
3487-
// Wait for the final result
3465+
// With the new behavior, retries continue in background
3466+
// Wait for all retries to complete
34883467
await vi.advanceTimersByTimeAsync(4)
34893468
expect(rendered.getByText('failureCount 4')).toBeInTheDocument()
34903469
expect(
@@ -3493,12 +3472,10 @@ describe('useQuery', () => {
34933472
expect(rendered.getByText('status error')).toBeInTheDocument()
34943473
expect(rendered.getByText('error fetching error 4')).toBeInTheDocument()
34953474

3496-
// Check if the query really stopped
3497-
await vi.advanceTimersByTimeAsync(10)
3498-
expect(rendered.getByText('failureCount 4')).toBeInTheDocument()
3499-
expect(
3500-
rendered.getByText('failureReason fetching error 4'),
3501-
).toBeInTheDocument()
3475+
// Verify all retries were attempted
3476+
expect(count).toBe(4)
3477+
3478+
visibilityMock.mockRestore()
35023479
})
35033480

35043481
it('should fetch on mount when a query was already created with setQueryData', async () => {

packages/solid-query/src/__tests__/useQuery.test.tsx

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3197,8 +3197,7 @@ describe('useQuery', () => {
31973197
expect(queryFn).toHaveBeenCalledTimes(2)
31983198
})
31993199

3200-
// See https://github.com/tannerlinsley/react-query/issues/160
3201-
it('should continue retry after focus regain', async () => {
3200+
it('should continue retry in background even when page is not focused', async () => {
32023201
const key = queryKey()
32033202

32043203
// make page unfocused
@@ -3233,37 +3232,8 @@ describe('useQuery', () => {
32333232
</QueryClientProvider>
32343233
))
32353234

3236-
// The query should display the first error result
3237-
await vi.waitFor(() =>
3238-
expect(rendered.getByText('failureCount 1')).toBeInTheDocument(),
3239-
)
3240-
await vi.waitFor(() =>
3241-
expect(
3242-
rendered.getByText('failureReason fetching error 1'),
3243-
).toBeInTheDocument(),
3244-
)
3245-
await vi.waitFor(() =>
3246-
expect(rendered.getByText('status pending')).toBeInTheDocument(),
3247-
)
3248-
await vi.waitFor(() =>
3249-
expect(rendered.getByText('error null')).toBeInTheDocument(),
3250-
)
3251-
3252-
// Check if the query really paused
3253-
await sleep(10)
3254-
await vi.waitFor(() =>
3255-
expect(rendered.getByText('failureCount 1')).toBeInTheDocument(),
3256-
)
3257-
await vi.waitFor(() =>
3258-
expect(
3259-
rendered.getByText('failureReason fetching error 1'),
3260-
).toBeInTheDocument(),
3261-
)
3262-
3263-
visibilityMock.mockRestore()
3264-
window.dispatchEvent(new Event('visibilitychange'))
3265-
3266-
// Wait for the final result
3235+
// With the new behavior, retries continue in background
3236+
// Wait for all retries to complete
32673237
await vi.waitFor(() =>
32683238
expect(rendered.getByText('failureCount 4')).toBeInTheDocument(),
32693239
)
@@ -3279,16 +3249,10 @@ describe('useQuery', () => {
32793249
expect(rendered.getByText('error fetching error 4')).toBeInTheDocument(),
32803250
)
32813251

3282-
// Check if the query really stopped
3283-
await sleep(10)
3284-
await vi.waitFor(() =>
3285-
expect(rendered.getByText('failureCount 4')).toBeInTheDocument(),
3286-
)
3287-
await vi.waitFor(() =>
3288-
expect(
3289-
rendered.getByText('failureReason fetching error 4'),
3290-
).toBeInTheDocument(),
3291-
)
3252+
// Verify all retries were attempted
3253+
expect(count).toBe(4)
3254+
3255+
visibilityMock.mockRestore()
32923256
})
32933257

32943258
it('should fetch on mount when a query was already created with setQueryData', async () => {

0 commit comments

Comments
 (0)