Skip to content

Commit 200b63a

Browse files
committed
fix(query-core): respect refetchIntervalInBackground option for query retries
1 parent ccedf33 commit 200b63a

File tree

4 files changed

+140
-3
lines changed

4 files changed

+140
-3
lines changed

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,4 +1304,134 @@ describe('query', () => {
13041304
data: 'data1',
13051305
})
13061306
})
1307+
1308+
test('should continue retry in background when refetchIntervalInBackground is true', async () => {
1309+
const key = queryKey()
1310+
1311+
// make page unfocused
1312+
const visibilityMock = mockVisibilityState('hidden')
1313+
1314+
let count = 0
1315+
let result
1316+
1317+
const promise = queryClient.fetchQuery({
1318+
queryKey: key,
1319+
queryFn: () => {
1320+
count++
1321+
1322+
if (count === 3) {
1323+
return `data${count}`
1324+
}
1325+
1326+
throw new Error(`error${count}`)
1327+
},
1328+
retry: 3,
1329+
retryDelay: 1,
1330+
refetchIntervalInBackground: true,
1331+
})
1332+
1333+
promise.then((data) => {
1334+
result = data
1335+
})
1336+
1337+
// Check if we do not have a result yet
1338+
expect(result).toBeUndefined()
1339+
1340+
// Query should continue retrying in background
1341+
await vi.advanceTimersByTimeAsync(50)
1342+
expect(result).toBe('data3')
1343+
1344+
// Reset visibilityState to original value
1345+
visibilityMock.mockRestore()
1346+
})
1347+
1348+
test('should pause retry when unfocused if refetchIntervalInBackground is false', async () => {
1349+
const key = queryKey()
1350+
1351+
// make page unfocused
1352+
const visibilityMock = mockVisibilityState('hidden')
1353+
1354+
let count = 0
1355+
let result
1356+
1357+
const promise = queryClient.fetchQuery({
1358+
queryKey: key,
1359+
queryFn: () => {
1360+
count++
1361+
1362+
if (count === 3) {
1363+
return `data${count}`
1364+
}
1365+
1366+
throw new Error(`error${count}`)
1367+
},
1368+
retry: 3,
1369+
retryDelay: 1,
1370+
refetchIntervalInBackground: false,
1371+
})
1372+
1373+
promise.then((data) => {
1374+
result = data
1375+
})
1376+
1377+
// Check if we do not have a result
1378+
expect(result).toBeUndefined()
1379+
1380+
// Check if the query is really paused
1381+
await vi.advanceTimersByTimeAsync(50)
1382+
expect(result).toBeUndefined()
1383+
1384+
// Reset visibilityState to original value
1385+
visibilityMock.mockRestore()
1386+
window.dispatchEvent(new Event('visibilitychange'))
1387+
1388+
// Query should now continue and resolve
1389+
await vi.advanceTimersByTimeAsync(50)
1390+
expect(result).toBe('data3')
1391+
})
1392+
1393+
test('should pause retry when unfocused if refetchIntervalInBackground is undefined (default behavior)', async () => {
1394+
const key = queryKey()
1395+
1396+
// make page unfocused
1397+
const visibilityMock = mockVisibilityState('hidden')
1398+
1399+
let count = 0
1400+
let result
1401+
1402+
const promise = queryClient.fetchQuery({
1403+
queryKey: key,
1404+
queryFn: () => {
1405+
count++
1406+
1407+
if (count === 3) {
1408+
return `data${count}`
1409+
}
1410+
1411+
throw new Error(`error${count}`)
1412+
},
1413+
retry: 3,
1414+
retryDelay: 1,
1415+
// refetchIntervalInBackground is not set (undefined by default)
1416+
})
1417+
1418+
promise.then((data) => {
1419+
result = data
1420+
})
1421+
1422+
// Check if we do not have a result
1423+
expect(result).toBeUndefined()
1424+
1425+
// Check if the query is really paused
1426+
await vi.advanceTimersByTimeAsync(50)
1427+
expect(result).toBeUndefined()
1428+
1429+
// Reset visibilityState to original value
1430+
visibilityMock.mockRestore()
1431+
window.dispatchEvent(new Event('visibilitychange'))
1432+
1433+
// Query should now continue and resolve
1434+
await vi.advanceTimersByTimeAsync(50)
1435+
expect(result).toBe('data3')
1436+
})
13071437
})

packages/query-core/src/query.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ export class Query<
390390
): Promise<TData> {
391391
if (
392392
this.state.fetchStatus !== 'idle' &&
393-
// If the promise in the retyer is already rejected, we have to definitely
393+
// If the promise in the retryer is already rejected, we have to definitely
394394
// re-start the fetch; there is a chance that the query is still in a
395395
// pending state when that happens
396396
this.#retryer?.status() !== 'rejected'
@@ -541,6 +541,7 @@ export class Query<
541541
retryDelay: context.options.retryDelay,
542542
networkMode: context.options.networkMode,
543543
canRun: () => true,
544+
refetchIntervalInBackground: this.options.refetchIntervalInBackground,
544545
})
545546

546547
try {

packages/query-core/src/retryer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { focusManager } from './focusManager'
21
import { onlineManager } from './onlineManager'
32
import { pendingThenable } from './thenable'
43
import { isServer, sleep } from './utils'
4+
import { focusManager } from './focusManager'
55
import type { Thenable } from './thenable'
66
import type { CancelOptions, DefaultError, NetworkMode } from './types'
77

@@ -18,6 +18,7 @@ interface RetryerConfig<TData = unknown, TError = DefaultError> {
1818
retryDelay?: RetryDelayValue<TError>
1919
networkMode: NetworkMode | undefined
2020
canRun: () => boolean
21+
refetchIntervalInBackground?: boolean
2122
}
2223

2324
export interface Retryer<TData = unknown> {
@@ -101,7 +102,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
101102
}
102103

103104
const canContinue = () =>
104-
focusManager.isFocused() &&
105+
(config.refetchIntervalInBackground === true || focusManager.isFocused()) &&
105106
(config.networkMode === 'always' || onlineManager.isOnline()) &&
106107
config.canRun()
107108

packages/query-core/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ export interface QueryOptions<
275275
* Maximum number of pages to store in the data of an infinite query.
276276
*/
277277
maxPages?: number
278+
/**
279+
* If set to `true`, the query will continue to refetch while their tab/window is in the background.
280+
* Defaults to `false`.
281+
*/
282+
refetchIntervalInBackground?: boolean
278283
}
279284

280285
export interface InitialPageParam<TPageParam = unknown> {

0 commit comments

Comments
 (0)