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

Conversation

Copy link

@Hellol77 Hellol77 commented Aug 13, 2025
edited by coderabbitai bot
Loading

fixed #8353

  • Instead of removing focusManager.isFocused(), update retryer.ts to check (config.refetchIntervalInBackground === true || focusManager.isFocused())
  • Passed the refetchIntervalInBackground option through query.ts.
  • Added the refetchIntervalInBackground property to the QueryOptions interface in types.ts.

Summary by CodeRabbit

  • New Features

    • Added a new Query option refetchIntervalInBackground (default: false) to allow scheduled refetches/retries to continue while the app/tab is in the background.
  • Tests

    • Added tests covering background refetch/retry behavior for enabled, disabled, and default cases to ensure pause/continue semantics based on focus and network state.

injae-kim reacted with thumbs up emoji injae-kim reacted with heart emoji injae-kim reacted with rocket emoji
Copy link

nx-cloud bot commented Aug 14, 2025
edited
Loading

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit b72137c

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ❌ Failed 3m 25s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 21s View ↗

☁️ Nx Cloud last updated this comment at 2025年09月01日 11:07:52 UTC

Copy link

pkg-pr-new bot commented Aug 14, 2025
edited
Loading

More templates
@tanstack/angular-query-devtools-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-devtools-experimental@9563
@tanstack/angular-query-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9563
@tanstack/eslint-plugin-query
npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9563
@tanstack/query-async-storage-persister
npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9563
@tanstack/query-broadcast-client-experimental
npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9563
@tanstack/query-core
npm i https://pkg.pr.new/@tanstack/query-core@9563
@tanstack/query-devtools
npm i https://pkg.pr.new/@tanstack/query-devtools@9563
@tanstack/query-persist-client-core
npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9563
@tanstack/query-sync-storage-persister
npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9563
@tanstack/react-query
npm i https://pkg.pr.new/@tanstack/react-query@9563
@tanstack/react-query-devtools
npm i https://pkg.pr.new/@tanstack/react-query-devtools@9563
@tanstack/react-query-next-experimental
npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9563
@tanstack/react-query-persist-client
npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9563
@tanstack/solid-query
npm i https://pkg.pr.new/@tanstack/solid-query@9563
@tanstack/solid-query-devtools
npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9563
@tanstack/solid-query-persist-client
npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9563
@tanstack/svelte-query
npm i https://pkg.pr.new/@tanstack/svelte-query@9563
@tanstack/svelte-query-devtools
npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9563
@tanstack/svelte-query-persist-client
npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9563
@tanstack/vue-query
npm i https://pkg.pr.new/@tanstack/vue-query@9563
@tanstack/vue-query-devtools
npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9563

commit: b72137c

Copy link
Collaborator

TkDodo commented Aug 14, 2025

plase have a look at the conflicts and failing tests

@Hellol77 Hellol77 force-pushed the fix/retry-background-focus-issue branch 2 times, most recently from 7250804 to 56049b5 Compare August 14, 2025 13:19
Copy link
Author

Hellol77 commented Aug 14, 2025
edited
Loading

I fixed the conflicts and test issues! thanks for review. @TkDodo

injae-kim and Han5991 reacted with thumbs up emoji

Copy link

coderabbitai bot commented Aug 18, 2025
edited
Loading

Walkthrough

Adds a Query option refetchIntervalInBackground and forwards it into the Retryer; Retryer.canContinue is updated so retries may run while the page is unfocused when that option is true. Tests added for true/false/undefined background retry behavior; a comment typo fixed.

Changes

Cohort / File(s) Summary
Retryer control-flow
packages/query-core/src/retryer.ts
Adds refetchIntervalInBackground?: boolean to RetryerConfig and updates canContinue to allow retries when that flag is true, while preserving networkMode/online checks; minor import reorder.
Options plumbing
packages/query-core/src/types.ts, packages/query-core/src/query.ts
Adds QueryOptions.refetchIntervalInBackground?: boolean to public types and forwards the option from Query.fetch into createRetryer; fixes a comment typo.
Tests: background retry behavior
packages/query-core/src/__tests__/query.test.tsx
Adds three tests covering retry behavior when refetchIntervalInBackground is true, false, and undefined while the page is unfocused.

Sequence Diagram(s)

sequenceDiagram
 autonumber
 participant Q as Query
 participant R as Retryer
 participant F as focusManager
 participant O as onlineManager
 Note over Q: start fetch with retry options (includes refetchIntervalInBackground)
 Q->>R: createRetryer({ retry, retryDelay, networkMode, refetchIntervalInBackground })
 loop retry loop
 R->>F: isFocused()
 R->>O: isOnline()
 alt (refetchIntervalInBackground == true OR focused) AND (networkMode == 'always' OR online)
 Note right of R #DFF7E0: Continue attempts
 R->>Q: attempt fetch
 alt success
 R-->>Q: resolve
 else failure
 R->>R: schedule retry after retryDelay
 end
 else paused
 Note right of R #FFEFD5: Pause until focus/online
 R-->>R: wait for visibilitychange/online
 end
 end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Retries continue in background when refetchIntervalInBackground: true with retry configured (#8353)
Default behavior: retries pause when tab unfocused and refetchIntervalInBackground is false/undefined (#8353)
Public API exposes refetchIntervalInBackground on queries (#8353)

I thump my paw at ticking clocks—
Retries hop on while the window locks.
Tabs may nap beneath the moon,
But fetches hum a steady tune.
Carrots fetched, no pause for night—🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2c351a4 and 51328d4.

📒 Files selected for processing (4)
  • packages/query-core/src/__tests__/query.test.tsx (1 hunks)
  • packages/query-core/src/query.ts (2 hunks)
  • packages/query-core/src/retryer.ts (3 hunks)
  • packages/query-core/src/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/query-core/src/types.ts
  • packages/query-core/src/retryer.ts
  • packages/query-core/src/query.ts
  • packages/query-core/src/tests/query.test.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
packages/solid-query/src/__tests__/useQuery.test.tsx (3)

3203-3205: Ensure visibility state is restored even on test failure (wrap in try/finally)

If an assertion fails, the mocked visibility may bleed into subsequent tests. Wrap the body in try/finally to guarantee cleanup.

- // make page unfocused
- const visibilityMock = mockVisibilityState('hidden')
+ // make page unfocused
+ const visibilityMock = mockVisibilityState('hidden')
+ try {
@@
- visibilityMock.mockRestore()
+ } finally {
+ visibilityMock.mockRestore()
+ }

Also applies to: 3255-3256


3209-3216: Avoid unnecessary generic on Promise.reject

Promise.reject<unknown>(...) doesn’t add value here and can confuse readers regarding the error type. Returning a rejected promise without the generic keeps intent clear while preserving the string error typing set for useQuery.

- queryFn: () => {
+ queryFn: () => {
 count++
- return Promise.reject<unknown>(`fetching error ${count}`)
+ return Promise.reject(`fetching error ${count}`)
 },

3237-3251: Optional: consolidate waits to reduce flakiness and execution time

You perform multiple sequential waitFor checks for related end-state assertions. Consolidating into one waitFor that verifies all required end-state texts reduces the number of polling cycles and makes the test slightly faster and less chatty.

- await vi.waitFor(() =>
- expect(rendered.getByText('failureCount 4')).toBeInTheDocument(),
- )
- await vi.waitFor(() =>
- expect(
- rendered.getByText('failureReason fetching error 4'),
- ).toBeInTheDocument(),
- )
- await vi.waitFor(() =>
- expect(rendered.getByText('status error')).toBeInTheDocument(),
- )
- await vi.waitFor(() =>
- expect(rendered.getByText('error fetching error 4')).toBeInTheDocument(),
- )
+ await vi.waitFor(() => {
+ expect(rendered.getByText('failureCount 4')).toBeInTheDocument()
+ expect(rendered.getByText('failureReason fetching error 4')).toBeInTheDocument()
+ expect(rendered.getByText('status error')).toBeInTheDocument()
+ expect(rendered.getByText('error fetching error 4')).toBeInTheDocument()
+ })
packages/react-query/src/__tests__/useQuery.test.tsx (1)

3434-3479: Guard visibility mock with try/finally to prevent cross-test leakage

If an assertion fails, visibilityMock.mockRestore() won't run and could affect subsequent tests. Wrap the test body in a try/finally.

 it('should continue retry in background even when page is not focused', async () => {
 const key = queryKey()
 // make page unfocused
 const visibilityMock = mockVisibilityState('hidden')
+ try {
 let count = 0
 function Page() {
 const query = useQuery<unknown, string>({
 queryKey: key,
 queryFn: () => {
 count++
 return Promise.reject<unknown>(`fetching error ${count}`)
 },
 retry: 3,
 retryDelay: 1,
 })
 return (
 <div>
 <div>error {String(query.error)}</div>
 <div>status {query.status}</div>
 <div>failureCount {query.failureCount}</div>
 <div>failureReason {query.failureReason}</div>
 </div>
 )
 }
 const rendered = renderWithClient(queryClient, <Page />)
 // With the new behavior, retries continue in background
 // Wait for all retries to complete
 await vi.advanceTimersByTimeAsync(4)
 expect(rendered.getByText('failureCount 4')).toBeInTheDocument()
 expect(
 rendered.getByText('failureReason fetching error 4'),
 ).toBeInTheDocument()
 expect(rendered.getByText('status error')).toBeInTheDocument()
 expect(rendered.getByText('error fetching error 4')).toBeInTheDocument()
 // Verify all retries were attempted
 expect(count).toBe(4)
-
- visibilityMock.mockRestore()
+ } finally {
+ visibilityMock.mockRestore()
+ }
 })
packages/query-core/src/__tests__/query.test.tsx (4)

61-99: Solid background-retry assertion; add try/finally around visibility mock

The updated expectation aligns with removing focus gating. Minor robustness: ensure the visibility mock is restored even on failure.

- const visibilityMock = mockVisibilityState('hidden')
+ const visibilityMock = mockVisibilityState('hidden')
+ try {
 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,
 })
 
 promise.then((data) => {
 result = data
 })
 
 // Check if we do not have a result initially
 expect(result).toBeUndefined()
 
 // With new behavior, retries continue in background
 // Wait for retries to complete
 await vi.advanceTimersByTimeAsync(10)
 expect(result).toBe('data3')
 expect(count).toBe(3)
-
- visibilityMock.mockRestore()
+ } finally {
+ visibilityMock.mockRestore()
+ }

149-178: New background-retry test looks good; ensure visibility cleanup via try/finally

Matches the core change to remove focus gating. Add try/finally so the visibility mock is always restored.

- const visibilityMock = mockVisibilityState('hidden')
+ const visibilityMock = mockVisibilityState('hidden')
+ try {
 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,
 })
 promise.then((data) => {
 result = data
 })
 // Check if we do not have a result initially
 expect(result).toBeUndefined()
 // Unlike the old behavior, retry should continue in background
 // Wait for retries to complete
 await vi.advanceTimersByTimeAsync(10)
 expect(result).toBe('data3')
 expect(count).toBe(3)
- visibilityMock.mockRestore()
+ } finally {
+ visibilityMock.mockRestore()
+ }

180-217: Simplify cancellation assertion and remove redundant checks

You can assert the cancellation more directly and drop the extra result plumbing and redundant instanceof Error check.

- it('should throw a CancelledError when a retrying query is cancelled', async () => {
+ it('should throw a CancelledError when a retrying query is cancelled', async () => {
 const key = queryKey()
 let count = 0
- let result: unknown
 
 const promise = queryClient.fetchQuery({
 queryKey: key,
 queryFn: (): Promise<unknown> => {
 count++
 throw new Error(`error${count}`)
 },
 retry: 3,
- retryDelay: 100, // Longer delay to allow cancellation
+ retryDelay: 100, // Longer delay to allow cancellation
 })
 
- promise.catch((data) => {
- result = data
- })
-
 const query = queryCache.find({ queryKey: key })!
 
 // Wait briefly for first failure and start of retry
 await vi.advanceTimersByTimeAsync(1)
 expect(result).toBeUndefined()
 
 // Cancel query during retry
 query.cancel()
 
- // Check if the error is set to the cancelled error
- try {
- await promise
- expect.unreachable()
- } catch {
- expect(result instanceof CancelledError).toBe(true)
- expect(result instanceof Error).toBe(true)
- }
+ // Assert promise rejects with CancelledError
+ await expect(promise).rejects.toBeInstanceOf(CancelledError)
 })

Note: if you keep the current style, consider adding await vi.advanceTimersByTimeAsync(0) after query.cancel() to flush microtasks, but it isn’t strictly necessary when awaiting the promise.


258-294: Good coverage of refetchInterval + retries in background; add subscription cleanup and try/finally

  • Capture and call the unsubscribe returned from subscribe.
  • Wrap with try/finally to always destroy the observer and restore visibility, even if an assertion fails.
 it('should continue refetchInterval with retries in background when tab is inactive', async () => {
 const key = queryKey()
- const visibilityMock = mockVisibilityState('hidden')
+ const visibilityMock = mockVisibilityState('hidden')
 
 let totalRequests = 0
 
 const queryObserver = new QueryObserver(queryClient, {
 queryKey: key,
 queryFn: () => {
 totalRequests++
 // Always fail to simulate network offline
 throw new Error(`Network error ${totalRequests}`)
 },
 refetchInterval: 60000,
 refetchIntervalInBackground: true,
 retry: 3,
 retryDelay: 1,
 })
 
- queryObserver.subscribe(() => {})
+ const unsubscribe = queryObserver.subscribe(() => {})
 
- // First interval: t=0 to t=60s (initial query + retries)
- await vi.advanceTimersByTimeAsync(60000)
- expect(totalRequests).toBe(4)
-
- // Second interval: t=60s to t=120s (refetch + retries)
- await vi.advanceTimersByTimeAsync(60000)
- expect(totalRequests).toBe(8)
-
- // Third interval: t=120s to t=180s (refetch + retries)
- await vi.advanceTimersByTimeAsync(60000)
- expect(totalRequests).toBe(12)
-
- queryObserver.destroy()
- visibilityMock.mockRestore()
+ try {
+ // First interval: t=0 to t=60s (initial query + retries)
+ await vi.advanceTimersByTimeAsync(60000)
+ expect(totalRequests).toBe(4)
+
+ // Second interval: t=60s to t=120s (refetch + retries)
+ await vi.advanceTimersByTimeAsync(60000)
+ expect(totalRequests).toBe(8)
+
+ // Third interval: t=120s to t=180s (refetch + retries)
+ await vi.advanceTimersByTimeAsync(60000)
+ expect(totalRequests).toBe(12)
+ } finally {
+ unsubscribe()
+ queryObserver.destroy()
+ visibilityMock.mockRestore()
+ }
 })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1e608b and b4c70f1.

📒 Files selected for processing (4)
  • packages/query-core/src/__tests__/query.test.tsx (6 hunks)
  • packages/query-core/src/retryer.ts (0 hunks)
  • packages/react-query/src/__tests__/useQuery.test.tsx (3 hunks)
  • packages/solid-query/src/__tests__/useQuery.test.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • packages/query-core/src/retryer.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/query-core/src/__tests__/query.test.tsx (3)
packages/query-core/src/queriesObserver.ts (1)
  • result (186-201)
packages/query-core/src/query.ts (1)
  • promise (198-200)
packages/query-core/src/retryer.ts (1)
  • CancelledError (57-65)
🔇 Additional comments (1)
packages/solid-query/src/__tests__/useQuery.test.tsx (1)

3200-3217: Test intent and expectations align with the new background-retry semantics

Good addition. The scenario accurately verifies that retries are not gated by focus anymore: failureCount/error state match the configured retry count (initial + 3 retries = 4). Assertions cover both failureCount and surfaced error, which is helpful.

Also applies to: 3235-3251

Copy link
Collaborator

TkDodo commented Aug 18, 2025

I discussed this with @DamianOsipiuk and we think the better fix would be to check the refetchIntervalInBackground prop additionally in the retryer instead of removing the check. That should also fix the issue.

We might remove the check in the next major version as it’s technically a breaking change.

injae-kim reacted with thumbs up emoji

@Hellol77 Hellol77 force-pushed the fix/retry-background-focus-issue branch from b4c70f1 to a76783d Compare August 19, 2025 14:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/query-core/src/__tests__/query.test.tsx (4)

89-97: Avoid magic timer values; flush timers deterministically

Using a fixed 10ms advance is brittle. Prefer running pending timers to completion in this test to avoid coupling to retryDelay:

-// With new behavior, retries continue in background
-// Wait for retries to complete
-await vi.advanceTimersByTimeAsync(10)
-expect(result).toBe('data3')
+// Flush scheduled retries deterministically
+await vi.runAllTimersAsync()
+expect(result).toBe('data3')

If you want to keep time-based advancing, compute from the configured retryDelay and number of retries to document intent (e.g., advance by retryDelay * 3 + epsilon).


149-178: Duplicate of the previous test; consider consolidating

This test appears to assert the same behavior as the earlier "continue retry ... while hidden" case with identical setup and expectations. Consider merging into a single, descriptively named test or parameterizing to reduce duplication and test time.

Example consolidation (rename earlier test and remove this one), or parameterize with it.each over an array of titles if you need both names for historical clarity.


180-217: Tighten cancellation assertions and timing

  • Use toBeInstanceOf for clarity/readability:
- expect(result instanceof CancelledError).toBe(true)
- expect(result instanceof Error).toBe(true)
+ expect(result).toBeInstanceOf(CancelledError)
  • To make "cancel during retry" explicit with retryDelay: 100, consider advancing to a point between attempts (e.g., 50ms) before cancelling:
-// Wait briefly for first failure and start of retry
-await vi.advanceTimersByTimeAsync(1)
+// Wait into the backoff window before the next retry
+await vi.advanceTimersByTimeAsync(50)

This reduces ambiguity between "after initial failure but before retry starts" vs "during a retry backoff window".


258-293: Good coverage of refetchIntervalInBackground; minor cleanup suggested

  • The test accurately validates background refetch+retry behavior when hidden with refetchIntervalInBackground: true. Nice.
  • Minor test hygiene: subscribe returns an unsubscribe function—prefer using it to clean up instead of destroy(), which is more forceful than needed here:
- queryObserver.subscribe(() => {})
+ const unsubscribe = queryObserver.subscribe(() => {})
 ...
- queryObserver.destroy()
+ unsubscribe()
  • Optional: make interval values self-documenting and avoid repeated literals:
- refetchInterval: 60000,
+ const interval = 60_000
+ ...
+ refetchInterval: interval,
...
- await vi.advanceTimersByTimeAsync(60000)
+ await vi.advanceTimersByTimeAsync(interval)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b4c70f1 and a76783d.

📒 Files selected for processing (4)
  • packages/query-core/src/__tests__/query.test.tsx (6 hunks)
  • packages/query-core/src/retryer.ts (0 hunks)
  • packages/react-query/src/__tests__/useQuery.test.tsx (3 hunks)
  • packages/solid-query/src/__tests__/useQuery.test.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • packages/query-core/src/retryer.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/solid-query/src/tests/useQuery.test.tsx
  • packages/react-query/src/tests/useQuery.test.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/query-core/src/__tests__/query.test.tsx (2)
packages/query-core/src/query.ts (1)
  • promise (198-200)
packages/query-core/src/retryer.ts (1)
  • CancelledError (57-65)
🔇 Additional comments (1)
packages/query-core/src/__tests__/query.test.tsx (1)

61-99: The search results didn’t reveal the Retryer implementation location directly. To determine whether config.canRun() bypasses focus gating unconditionally or only when refetchIntervalInBackground is enabled, I’ll need to locate:

  1. Where config.canRun is defined/passed into Retryer.
  2. How focus gating is implemented in canRun based on visibility state and refetchIntervalInBackground.

Let me search for where Retryer is constructed to inspect the config object.

@Hellol77 Hellol77 changed the title (削除) fix(query-core): remove focus check from retryer for refetchIntervallnBackground (削除ここまで) (追記) fix(query-core): focus check issue from retryer for refetchIntervallnBackground (追記ここまで) Aug 19, 2025
@Hellol77 Hellol77 force-pushed the fix/retry-background-focus-issue branch from a76783d to a5ad6ab Compare August 19, 2025 15:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/query-core/src/types.ts (2)

279-281: TSDoc: clarify that this also affects retry gating, not just interval refetching

With this PR, the flag now also allows retries to continue while unfocused. Consider clarifying the description and a tiny grammar tweak.

Apply this diff to update the comment:

 /**
- * If set to `true`, the query will continue to refetch while their tab/window is in the background.
- * Defaults to `false`.
+ * If set to `true`, the query will continue to refetch and retry even when the tab/window is in the background.
+ * Defaults to `false`.
 */

278-283: Avoid duplicate declarations between QueryOptions and QueryObserverOptions

Now that QueryObserverOptions extends QueryOptions and this property lives on QueryOptions, the explicit redeclaration on QueryObserverOptions (Line 354–357) is redundant and risks docs drift.

Two options:

  • Remove the property from QueryObserverOptions and let it inherit from QueryOptions (preferred).
  • If you want to keep docs colocated on the observer interface, remove it from QueryOptions and fetch it from the first observer’s options when wiring the retryer (heavier change).

If you opt for the first approach, remove the member from QueryObserverOptions (outside this hunk).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a76783d and a5ad6ab.

📒 Files selected for processing (4)
  • packages/query-core/src/__tests__/query.test.tsx (1 hunks)
  • packages/query-core/src/query.ts (2 hunks)
  • packages/query-core/src/retryer.ts (3 hunks)
  • packages/query-core/src/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/query-core/src/tests/query.test.tsx
  • packages/query-core/src/retryer.ts
🔇 Additional comments (4)
packages/query-core/src/types.ts (1)

278-283: Adding refetchIntervalInBackground to QueryOptions is the right call

This unblocks passing the option into the retryer from Query, which only has QueryOptions available. Matches the PR’s objective and is backward-compatible.

packages/query-core/src/query.ts (3)

381-385: Typo fix in comment — LGTM

"retryer" spelling fix reads well and clarifies the guard’s intent.


524-525: Plumbing refetchIntervalInBackground into createRetryer is correct

This is the crucial connection so canContinue can honor background retries. Looks good.


524-525: No changes needed: refetchIntervalInBackground is present and canContinue implements the intended semantics.

The RetryerConfig interface declares:

refetchIntervalInBackground?: boolean

and canContinue is defined as:

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

This matches the requested behavior.

Copy link
Author

Got it, I updated this PR to add a check for the refetchIntervalInBackground prop in the retryer instead of removing the focus check. @TkDodo

injae-kim reacted with thumbs up emoji injae-kim reacted with heart emoji

@Hellol77 Hellol77 force-pushed the fix/retry-background-focus-issue branch from a5ad6ab to 2c351a4 Compare August 21, 2025 01:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/query-core/src/types.ts (2)

278-283: Clarify that this flag governs retries as well, and avoid duplicate declarations drifting over time

Good addition. Since retryer.ts now uses this option to allow background retries, the JSDoc should reflect that it affects both retries and interval refetches. Also, this property is declared here and again in QueryObserverOptions (Lines 354-357) with slightly different wording; consider consolidating to a single source to prevent future drift.

Apply this doc tweak within the current hunk:

 /**
- * If set to `true`, the query will continue to refetch while their tab/window is in the background.
- * Defaults to `false`.
+ * If set to `true`, the query will continue retries and refetches while the tab/window is in the background.
+ * Also influences retry behavior: background retries will proceed even when unfocused. Defaults to `false`.
 */
 refetchIntervalInBackground?: boolean

Optional consolidation (follow-up): Remove the duplicate property from QueryObserverOptions and rely on inheritance, or keep both but make their JSDoc identical. If your docs rely on the sub-interface re-declaration, keep it and sync the wording (see next comment).


354-357: Keep wording consistent here to match the new behavior

Mirror the updated semantics so both declarations say the same thing.

Suggested text:

/**
 * If set to `true`, the query will continue retries and refetches while the tab/window is in the background.
 * Defaults to `false`.
 */
refetchIntervalInBackground?: boolean
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a5ad6ab and 2c351a4.

📒 Files selected for processing (4)
  • packages/query-core/src/__tests__/query.test.tsx (1 hunks)
  • packages/query-core/src/query.ts (2 hunks)
  • packages/query-core/src/retryer.ts (3 hunks)
  • packages/query-core/src/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/query-core/src/retryer.ts
  • packages/query-core/src/tests/query.test.tsx
  • packages/query-core/src/query.ts
🔇 Additional comments (1)
packages/query-core/src/types.ts (1)

278-283: Verified refetchIntervalInBackground forwarding and guard logic

  • In packages/query-core/src/query.ts (around line 524), this.options.refetchIntervalInBackground is passed directly into createRetryer as refetchIntervalInBackground: this.options.refetchIntervalInBackground.
  • In packages/query-core/src/retryer.ts (lines 103–106), the canContinue guard correctly checks (config.refetchIntervalInBackground === true || focusManager.isFocused()) before proceeding.

The plumbing matches the intended behavior.

@Hellol77 Hellol77 force-pushed the fix/retry-background-focus-issue branch from 2c351a4 to 51328d4 Compare August 31, 2025 15:22
* 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@TkDodo TkDodo TkDodo approved these changes

@coderabbitai coderabbitai[bot] coderabbitai[bot] left review comments

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Polling Stops with refetchIntervalInBackground and Retry When Tab Is Inactive
2 participants

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