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

fix(query-core): focus check issue from retryer for refetchIntervallnBackground #9563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Hellol77 wants to merge 2 commits into TanStack:main
base: main
Choose a base branch
Loading
from Hellol77:fix/retry-background-focus-issue
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
130 changes: 130 additions & 0 deletions packages/query-core/src/__tests__/query.test.tsx
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -1242,4 +1242,134 @@ describe('query', () => {
data: 'data1',
})
})

test('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()
})

test('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')
})

test('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
View file Open in desktop
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 @@ -529,6 +529,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
View file Open in desktop
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 @@ -101,7 +102,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
View file Open in desktop
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm we shouldn’t really add this to QueryOptions as it’s an observer level property.

Now that I’m seeing this, I don’t think it’s "fixable" with an additional check because you can have different observers with different values for refetchIntervalInBackground.

I think the best step forward is to just remove the check in v6 and document the current behaviour for v5.

}

export interface InitialPageParam<TPageParam = unknown> {
Expand Down
Loading

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