Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /