Skip to content

Conversation

@xiangnuans
Copy link

@xiangnuans xiangnuans commented Nov 27, 2025

error (#9728)

🎯 Changes

  • Modified shouldFetchOn in packages/query-core/src/queryObserver.ts:
    • Added a check for query.state.status === 'error'.
    • This ensures that when a component mounts or explicitly requests a refetch (e.g. refetchOnWindowFocus, refetchOnMount, or manual retry via ErrorBoundary), it will trigger a fetch if the query is in an error state, even if the data is technically considered "fresh" (not stale) due to staleTime: Infinity.
  • Added regression test in packages/react-query/src/__tests__/issue-9728.test.tsx to reproduce the issue and verify the fix.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • Queries now refetch on mount or retry when in an error state, even if existing data is not stale, when refetch policy permits.
  • Tests

    • Added test coverage for error state refetch behavior with error boundaries.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Nov 27, 2025

🦋 Changeset detected

Latest commit: 233d48e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
@tanstack/query-core Patch
@tanstack/angular-query-experimental Patch
@tanstack/query-async-storage-persister Patch
@tanstack/query-broadcast-client-experimental Patch
@tanstack/query-persist-client-core Patch
@tanstack/query-sync-storage-persister Patch
@tanstack/react-query Patch
@tanstack/solid-query Patch
@tanstack/svelte-query Patch
@tanstack/vue-query Patch
@tanstack/angular-query-persist-client Patch
@tanstack/react-query-persist-client Patch
@tanstack/solid-query-persist-client Patch
@tanstack/svelte-query-persist-client Patch
@tanstack/react-query-devtools Patch
@tanstack/react-query-next-experimental Patch
@tanstack/solid-query-devtools Patch
@tanstack/svelte-query-devtools Patch
@tanstack/vue-query-devtools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

This PR patches @tanstack/query-core to ensure queries refetch on mount or retry when in an error state, even if existing data is not stale. The fix modifies the shouldFetchOn logic in queryObserver to treat error status as a trigger for fetching, alongside the existing stale-based logic, and includes a comprehensive test case validating the behavior.

Changes

Cohort / File(s) Summary
Changeset documentation
.changeset/open-keys-create.md
New changeset entry documenting patch-level version bump and fix for refetch-on-mount behavior with error states.
Query observer error-state handling
packages/query-core/src/queryObserver.ts
Modified shouldFetchOn condition to treat queries with status: 'error' as candidates for fetching when refetch policy enables it, in addition to stale-based checks.
Test for issue 9728
packages/react-query/src/__tests__/issue-9728.test.tsx
New test file validating error-state refetch behavior with QueryErrorResetBoundary, error boundary, and remount cycles; verifies queries fetch successfully after error recovery.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • The core logic change in queryObserver.ts is a single-condition addition; verify the error-state check integrates correctly with existing stale logic.
  • The test file provides good coverage but requires understanding of error boundary, QueryErrorResetBoundary, and Suspense interaction patterns.

Suggested labels

package: query-core, package: react-query

Suggested reviewers

  • TkDodo

Poem

🐰 A query in error now hops with new flair,
Refetching on mount without a care—
No stale-data walls can hold it back,
Fresh data flows along the track! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: ensuring queries refetch on mount/retry when status is error, directly addressing issue #9728.
Description check ✅ Passed The PR description is well-structured with all required sections completed: Changes section documents the modifications with clear rationale, Checklist items are marked complete, and Release Impact items are properly checked indicating a changeset was generated.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@xiangnuans xiangnuans changed the title fix(query-core): ensure query refetches on mount/retry when status is… fix(query-core): ensure query refetches on mount/retry when status is error (#9728) Nov 27, 2025
Copy link
Contributor

@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 (1)
.changeset/open-keys-create.md (1)

1-5: Changeset scope and message look accurate; optionally mention focus/reconnect.

Patch scope to @tanstack/query-core and the summary align with the shouldFetchOn change. Note that the implementation also affects refetch on window focus and reconnect via the same helper, which is fine but not called out here; up to you if you want to spell that out in the note.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aba3260 and d10cca3.

📒 Files selected for processing (3)
  • .changeset/open-keys-create.md (1 hunks)
  • packages/query-core/src/queryObserver.ts (1 hunks)
  • packages/react-query/src/__tests__/issue-9728.test.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.
📚 Learning: 2025-11-22T09:06:05.219Z
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.

Applied to files:

  • packages/react-query/src/__tests__/issue-9728.test.tsx
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
Repo: TanStack/query PR: 9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • .changeset/open-keys-create.md
🧬 Code graph analysis (2)
packages/query-core/src/queryObserver.ts (1)
packages/query-core/src/query.ts (1)
  • isStale (296-306)
packages/react-query/src/__tests__/issue-9728.test.tsx (1)
packages/query-core/src/queryObserver.ts (1)
  • refetch (298-304)
🔇 Additional comments (4)
packages/query-core/src/queryObserver.ts (1)

771-791: Error-aware shouldFetchOn logic matches the intended fix.

Adding query.state.status === 'error' to the fetch predicate ensures that enabled queries with non‑static staleTime will refetch on mount/focus/reconnect when they are in an error state, even if their data isn’t time‑stale, which is exactly what issue 9728 describes. Guarding with value !== false preserves opt‑out via refetchOn* = false, and the existing staleTime === 'static' early return means static queries keep their previous behavior.

packages/react-query/src/__tests__/issue-9728.test.tsx (3)

16-36: Deterministic queryFn and client config nicely model the regression.

Using a vi.fn with a count‑based async implementation plus retry: false and staleTime: Infinity provides a clear, deterministic setup for success→error→success, matching the reported bug scenario.


37-78: Error boundary + reset wiring correctly exercises “retry via remount”.

Page throwing via throwOnError: true under ErrorBoundary and QueryErrorResetBoundary, with the Retry button calling resetErrorBoundary, ensures the query is remounted after an error in the presence of existing data—exactly the path the core fix targets.


88-107: Assertions cover initial success, error transition, and final successful refetch.

The three phases—first render success (1 call), refetch leading to error UI (2 calls), and Retry leading to “Success 3” (3 calls)—give good coverage for the bug and will regress if shouldFetchOn stops refetching on error with non‑stale data.

@TkDodo TkDodo linked an issue Dec 28, 2025 that may be closed by this pull request
@nx-cloud
Copy link

nx-cloud bot commented Dec 28, 2025

🤖 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 d5caeb8

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

☁️ Nx Cloud last updated this comment at 2025-12-28 16:03:45 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 28, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9927

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9927

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9927

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9927

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9927

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9927

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9927

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9927

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9927

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9927

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9927

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9927

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9927

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9927

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9927

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9927

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9927

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9927

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9927

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9927

commit: 12e4897

Comment on lines 784 to 788
return (
value === 'always' ||
(value !== false &&
(isStale(query, options) || query.state.status === 'error'))
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can make the argument that if a query errors and has data, that this data should be considered stale. However, this check here doesn’t convey that and likely misses a couple of cases, e.g. staleTime: 'static', which definitely shouldn't be refetched.

We do however have the option to mark a query as invalidated, and then the functions isStale and isStaleByTime on a query both check that in the correct order.

So maybe the right fix would be to just set isInvalidated in the reducer when we get an error:

I think setting it to:

// flag existing data as invalidated if we get a background error
isInvalidated: true,

should suffice because if we get an error and have no data yet, we’re always implicitly stale

Copy link
Author

Choose a reason for hiding this comment

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

Good point — I agree that semantically, “error with existing data” could be modeled as invalidated, and that would keep all stale logic centralized in isStale / isStaleByTime.

For this PR, I tried to keep the change narrowly scoped to fixing the concrete regression in #9728 via shouldFetchOn, without altering global invalidation semantics (which could have broader effects, e.g. with staleTime: 'static').

That said, I do think invalidating on background errors could be a cleaner long-term model. Happy to explore that in a follow-up PR if that aligns with the direction you’d prefer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah I’d prefer this direction really because I don’t think I can merge this PR as it would have the regression of queries with staleTime: 'static' getting refetched if they have an error even though they shouldn't. The flow is:

  • staleTime: 'static'
  • fetch successfully
  • re-fetch with an error

now this solution would then return true from refetchOn, but the solution that sets isInvalidated would not.

If you want, you can change the approach in this PR or file a new one.

Copy link
Collaborator

Choose a reason for hiding this comment

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

would also be great to have a test for this flow ⬆️

Copy link
Author

Choose a reason for hiding this comment

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

Got it — thanks for the clarification, that makes sense 👍
I agree this would be a regression for staleTime: 'static', and the invalidation approach is the right abstraction.

I’ll update this PR to:

  • remove the observer-level status === 'error' check
  • instead mark queries as isInvalidated on background errors in the reducer
  • add a regression test for the staleTime: 'static' → success → error flow you described

Will push an update shortly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

i think your fix is correct but we can simplify the code by just doing isInvalidated: true unconditionally. The reason is that if we have no data, the query is always considered stale:

isStaleByTime(staleTime: StaleTime = 0): boolean {
// no data is always stale
if (this.state.data === undefined) {
return true
}

so setting isInvalidate: true if we have no data doesn’t matter. Maybe leave a comment though :)

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense! Since isStaleByTime already treats queries with no data as stale, we can simplify this unconditionally. Updated accordingly.

xiangnuans and others added 3 commits December 28, 2025 23:01
…king error status

- Remove error status check in shouldFetchOn
- Set isInvalidated: true in reducer when background error occurs (only if data exists)
- Add tests for staleTime: 'static' and non-static queries with background errors

This approach centralizes stale logic in isStale/isStaleByTime and prevents
regression where staleTime: 'static' queries would incorrectly refetch on
window focus after a background error.
TkDodo and others added 3 commits December 28, 2025 17:03
Set isInvalidated: true unconditionally since queries with no data
are always considered stale per isStaleByTime logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Query caches error result and never calls queryFn

2 participants