Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions packages/query-core/src/__tests__/query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1192,4 +1192,135 @@ describe('query', () => {
expect(initialDataFn).toHaveBeenCalledTimes(1)
expect(query.state.data).toBe('initial data')
})


it('should continue retry in background when refetchIntervalInBackground is true', async () => {
const key = queryKey()

// make page unfocused
const visibilityMock = mockVisibilityState('hidden')

let count = 0
let result

const promise = queryClient.fetchQuery({
queryKey: key,
queryFn: () => {
count++

if (count === 3) {
return `data${count}`
}

throw new Error(`error${count}`)
},
retry: 3,
retryDelay: 1,
refetchIntervalInBackground: true,
})

promise.then((data) => {
result = data
})

// Check if we do not have a result yet
expect(result).toBeUndefined()

// Query should continue retrying in background
await vi.advanceTimersByTimeAsync(50)
expect(result).toBe('data3')

// Reset visibilityState to original value
visibilityMock.mockRestore()
})

it('should pause retry when unfocused if refetchIntervalInBackground is false', async () => {
const key = queryKey()

// make page unfocused
const visibilityMock = mockVisibilityState('hidden')

let count = 0
let result

const promise = queryClient.fetchQuery({
queryKey: key,
queryFn: () => {
count++

if (count === 3) {
return `data${count}`
}

throw new Error(`error${count}`)
},
retry: 3,
retryDelay: 1,
refetchIntervalInBackground: false,
})

promise.then((data) => {
result = data
})

// Check if we do not have a result
expect(result).toBeUndefined()

// Check if the query is really paused
await vi.advanceTimersByTimeAsync(50)
expect(result).toBeUndefined()

// Reset visibilityState to original value
visibilityMock.mockRestore()
window.dispatchEvent(new Event('visibilitychange'))

// Query should now continue and resolve
await vi.advanceTimersByTimeAsync(50)
expect(result).toBe('data3')
})

it('should pause retry when unfocused if refetchIntervalInBackground is undefined (default behavior)', async () => {
const key = queryKey()

// make page unfocused
const visibilityMock = mockVisibilityState('hidden')

let count = 0
let result

const promise = queryClient.fetchQuery({
queryKey: key,
queryFn: () => {
count++

if (count === 3) {
return `data${count}`
}

throw new Error(`error${count}`)
},
retry: 3,
retryDelay: 1,
// refetchIntervalInBackground is not set (undefined by default)
})

promise.then((data) => {
result = data
})

// Check if we do not have a result
expect(result).toBeUndefined()

// Check if the query is really paused
await vi.advanceTimersByTimeAsync(50)
expect(result).toBeUndefined()

// Reset visibilityState to original value
visibilityMock.mockRestore()
window.dispatchEvent(new Event('visibilitychange'))

// Query should now continue and resolve
await vi.advanceTimersByTimeAsync(50)
expect(result).toBe('data3')
})
})
3 changes: 2 additions & 1 deletion packages/query-core/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ export class Query<
): Promise<TData> {
if (
this.state.fetchStatus !== 'idle' &&
// If the promise in the retyer is already rejected, we have to definitely
// If the promise in the retryer is already rejected, we have to definitely
// re-start the fetch; there is a chance that the query is still in a
// pending state when that happens
this.#retryer?.status() !== 'rejected'
Expand Down Expand Up @@ -521,6 +521,7 @@ export class Query<
retryDelay: context.options.retryDelay,
networkMode: context.options.networkMode,
canRun: () => true,
refetchIntervalInBackground: this.options.refetchIntervalInBackground,
})

try {
Expand Down
5 changes: 3 additions & 2 deletions packages/query-core/src/retryer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { focusManager } from './focusManager'
import { onlineManager } from './onlineManager'
import { pendingThenable } from './thenable'
import { isServer, sleep } from './utils'
import { focusManager } from './focusManager'
import type { Thenable } from './thenable'
import type { CancelOptions, DefaultError, NetworkMode } from './types'

Expand All @@ -18,6 +18,7 @@ interface RetryerConfig<TData = unknown, TError = DefaultError> {
retryDelay?: RetryDelayValue<TError>
networkMode: NetworkMode | undefined
canRun: () => boolean
refetchIntervalInBackground?: boolean
}

export interface Retryer<TData = unknown> {
Expand Down Expand Up @@ -100,7 +101,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
}

const canContinue = () =>
focusManager.isFocused() &&
(config.refetchIntervalInBackground === true || focusManager.isFocused()) &&
(config.networkMode === 'always' || onlineManager.isOnline()) &&
config.canRun()

Expand Down
5 changes: 5 additions & 0 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ export interface QueryOptions<
* Maximum number of pages to store in the data of an infinite query.
*/
maxPages?: number
/**
* If set to `true`, the query will continue to refetch while their tab/window is in the background.
* Defaults to `false`.
*/
refetchIntervalInBackground?: boolean
}

export interface InitialPageParam<TPageParam = unknown> {
Expand Down