Skip to content

Commit 51328d4

Browse files
committed
fix(query-core): respect refetchIntervalInBackground option for query retries
1 parent b998f68 commit 51328d4

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
@@ -1242,4 +1242,134 @@ describe('query', () => {
12421242
data: 'data1',
12431243
})
12441244
})
1245+
1246+
test('should continue retry in background when refetchIntervalInBackground is true', async () => {
1247+
const key = queryKey()
1248+
1249+
// make page unfocused
1250+
const visibilityMock = mockVisibilityState('hidden')
1251+
1252+
let count = 0
1253+
let result
1254+
1255+
const promise = queryClient.fetchQuery({
1256+
queryKey: key,
1257+
queryFn: () => {
1258+
count++
1259+
1260+
if (count === 3) {
1261+
return `data${count}`
1262+
}
1263+
1264+
throw new Error(`error${count}`)
1265+
},
1266+
retry: 3,
1267+
retryDelay: 1,
1268+
refetchIntervalInBackground: true,
1269+
})
1270+
1271+
promise.then((data) => {
1272+
result = data
1273+
})
1274+
1275+
// Check if we do not have a result yet
1276+
expect(result).toBeUndefined()
1277+
1278+
// Query should continue retrying in background
1279+
await vi.advanceTimersByTimeAsync(50)
1280+
expect(result).toBe('data3')
1281+
1282+
// Reset visibilityState to original value
1283+
visibilityMock.mockRestore()
1284+
})
1285+
1286+
test('should pause retry when unfocused if refetchIntervalInBackground is false', async () => {
1287+
const key = queryKey()
1288+
1289+
// make page unfocused
1290+
const visibilityMock = mockVisibilityState('hidden')
1291+
1292+
let count = 0
1293+
let result
1294+
1295+
const promise = queryClient.fetchQuery({
1296+
queryKey: key,
1297+
queryFn: () => {
1298+
count++
1299+
1300+
if (count === 3) {
1301+
return `data${count}`
1302+
}
1303+
1304+
throw new Error(`error${count}`)
1305+
},
1306+
retry: 3,
1307+
retryDelay: 1,
1308+
refetchIntervalInBackground: false,
1309+
})
1310+
1311+
promise.then((data) => {
1312+
result = data
1313+
})
1314+
1315+
// Check if we do not have a result
1316+
expect(result).toBeUndefined()
1317+
1318+
// Check if the query is really paused
1319+
await vi.advanceTimersByTimeAsync(50)
1320+
expect(result).toBeUndefined()
1321+
1322+
// Reset visibilityState to original value
1323+
visibilityMock.mockRestore()
1324+
window.dispatchEvent(new Event('visibilitychange'))
1325+
1326+
// Query should now continue and resolve
1327+
await vi.advanceTimersByTimeAsync(50)
1328+
expect(result).toBe('data3')
1329+
})
1330+
1331+
test('should pause retry when unfocused if refetchIntervalInBackground is undefined (default behavior)', async () => {
1332+
const key = queryKey()
1333+
1334+
// make page unfocused
1335+
const visibilityMock = mockVisibilityState('hidden')
1336+
1337+
let count = 0
1338+
let result
1339+
1340+
const promise = queryClient.fetchQuery({
1341+
queryKey: key,
1342+
queryFn: () => {
1343+
count++
1344+
1345+
if (count === 3) {
1346+
return `data${count}`
1347+
}
1348+
1349+
throw new Error(`error${count}`)
1350+
},
1351+
retry: 3,
1352+
retryDelay: 1,
1353+
// refetchIntervalInBackground is not set (undefined by default)
1354+
})
1355+
1356+
promise.then((data) => {
1357+
result = data
1358+
})
1359+
1360+
// Check if we do not have a result
1361+
expect(result).toBeUndefined()
1362+
1363+
// Check if the query is really paused
1364+
await vi.advanceTimersByTimeAsync(50)
1365+
expect(result).toBeUndefined()
1366+
1367+
// Reset visibilityState to original value
1368+
visibilityMock.mockRestore()
1369+
window.dispatchEvent(new Event('visibilitychange'))
1370+
1371+
// Query should now continue and resolve
1372+
await vi.advanceTimersByTimeAsync(50)
1373+
expect(result).toBe('data3')
1374+
})
12451375
})

packages/query-core/src/query.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export class Query<
378378
): Promise<TData> {
379379
if (
380380
this.state.fetchStatus !== 'idle' &&
381-
// If the promise in the retyer is already rejected, we have to definitely
381+
// If the promise in the retryer is already rejected, we have to definitely
382382
// re-start the fetch; there is a chance that the query is still in a
383383
// pending state when that happens
384384
this.#retryer?.status() !== 'rejected'
@@ -529,6 +529,7 @@ export class Query<
529529
retryDelay: context.options.retryDelay,
530530
networkMode: context.options.networkMode,
531531
canRun: () => true,
532+
refetchIntervalInBackground: this.options.refetchIntervalInBackground,
532533
})
533534

534535
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)