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 2c351a4

Browse files
committed
fix(query-core): respect refetchIntervalInBackground option for query retries
1 parent a1b1279 commit 2c351a4

File tree

4 files changed

+141
-3
lines changed

4 files changed

+141
-3
lines changed

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

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,4 +1192,135 @@ describe('query', () => {
11921192
expect(initialDataFn).toHaveBeenCalledTimes(1)
11931193
expect(query.state.data).toBe('initial data')
11941194
})
1195+
1196+
1197+
it('should continue retry in background when refetchIntervalInBackground is true', async () => {
1198+
const key = queryKey()
1199+
1200+
// make page unfocused
1201+
const visibilityMock = mockVisibilityState('hidden')
1202+
1203+
let count = 0
1204+
let result
1205+
1206+
const promise = queryClient.fetchQuery({
1207+
queryKey: key,
1208+
queryFn: () => {
1209+
count++
1210+
1211+
if (count === 3) {
1212+
return `data${count}`
1213+
}
1214+
1215+
throw new Error(`error${count}`)
1216+
},
1217+
retry: 3,
1218+
retryDelay: 1,
1219+
refetchIntervalInBackground: true,
1220+
})
1221+
1222+
promise.then((data) => {
1223+
result = data
1224+
})
1225+
1226+
// Check if we do not have a result yet
1227+
expect(result).toBeUndefined()
1228+
1229+
// Query should continue retrying in background
1230+
await vi.advanceTimersByTimeAsync(50)
1231+
expect(result).toBe('data3')
1232+
1233+
// Reset visibilityState to original value
1234+
visibilityMock.mockRestore()
1235+
})
1236+
1237+
it('should pause retry when unfocused if refetchIntervalInBackground is false', async () => {
1238+
const key = queryKey()
1239+
1240+
// make page unfocused
1241+
const visibilityMock = mockVisibilityState('hidden')
1242+
1243+
let count = 0
1244+
let result
1245+
1246+
const promise = queryClient.fetchQuery({
1247+
queryKey: key,
1248+
queryFn: () => {
1249+
count++
1250+
1251+
if (count === 3) {
1252+
return `data${count}`
1253+
}
1254+
1255+
throw new Error(`error${count}`)
1256+
},
1257+
retry: 3,
1258+
retryDelay: 1,
1259+
refetchIntervalInBackground: false,
1260+
})
1261+
1262+
promise.then((data) => {
1263+
result = data
1264+
})
1265+
1266+
// Check if we do not have a result
1267+
expect(result).toBeUndefined()
1268+
1269+
// Check if the query is really paused
1270+
await vi.advanceTimersByTimeAsync(50)
1271+
expect(result).toBeUndefined()
1272+
1273+
// Reset visibilityState to original value
1274+
visibilityMock.mockRestore()
1275+
window.dispatchEvent(new Event('visibilitychange'))
1276+
1277+
// Query should now continue and resolve
1278+
await vi.advanceTimersByTimeAsync(50)
1279+
expect(result).toBe('data3')
1280+
})
1281+
1282+
it('should pause retry when unfocused if refetchIntervalInBackground is undefined (default behavior)', async () => {
1283+
const key = queryKey()
1284+
1285+
// make page unfocused
1286+
const visibilityMock = mockVisibilityState('hidden')
1287+
1288+
let count = 0
1289+
let result
1290+
1291+
const promise = queryClient.fetchQuery({
1292+
queryKey: key,
1293+
queryFn: () => {
1294+
count++
1295+
1296+
if (count === 3) {
1297+
return `data${count}`
1298+
}
1299+
1300+
throw new Error(`error${count}`)
1301+
},
1302+
retry: 3,
1303+
retryDelay: 1,
1304+
// refetchIntervalInBackground is not set (undefined by default)
1305+
})
1306+
1307+
promise.then((data) => {
1308+
result = data
1309+
})
1310+
1311+
// Check if we do not have a result
1312+
expect(result).toBeUndefined()
1313+
1314+
// Check if the query is really paused
1315+
await vi.advanceTimersByTimeAsync(50)
1316+
expect(result).toBeUndefined()
1317+
1318+
// Reset visibilityState to original value
1319+
visibilityMock.mockRestore()
1320+
window.dispatchEvent(new Event('visibilitychange'))
1321+
1322+
// Query should now continue and resolve
1323+
await vi.advanceTimersByTimeAsync(50)
1324+
expect(result).toBe('data3')
1325+
})
11951326
})

‎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'
@@ -521,6 +521,7 @@ export class Query<
521521
retryDelay: context.options.retryDelay,
522522
networkMode: context.options.networkMode,
523523
canRun: () => true,
524+
refetchIntervalInBackground: this.options.refetchIntervalInBackground,
524525
})
525526

526527
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> {
@@ -100,7 +101,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
100101
}
101102

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

‎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 によって変換されたページ (->オリジナル) /