fix(query-core): fix combine not updating when queries change with stable reference
🎯 Changes
Fixes #8781
Problem
With stable combine, results lag one step behind when queries change.
This happens because getOptimisticResult runs before setQueries updates this.#result.
Solution
Pass queryHashes to #combineResult to detect query changes before this.#result is updated.
✅ Checklist
- [x] I have followed the steps in the Contributing guide.
- [x] I have tested this code locally with
pnpm run test:pr.
🚀 Release Impact
- [x] 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
- Fixed an issue where combined query results failed to update when the number of queries changed dynamically. The stable combine reference now properly responds to changes in query composition, ensuring accurate aggregated results when queries are added, removed, or replaced at runtime.
✏️ Tip: You can customize this high-level summary in your review settings.
🦋 Changeset detected
Latest commit: 89db791104f4435887de45f9eb4ffdd07da0bdb9
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
Walkthrough
This PR fixes a bug where a stable combine reference in QueriesObserver was not being re-invoked when the underlying queries changed dynamically. The fix adds query hash tracking to detect query changes and triggers combine re-evaluation accordingly.
Changes
| Cohort / File(s) | Summary |
|---|---|
Changeset .changeset/swift-brooms-teach.md |
Patch version bump for @tanstack/query-core with description of fix for stable combine reference updating. |
Query Core Tests packages/query-core/src/__tests__/queriesObserver.test.tsx |
Adds comprehensive test cases for QueriesObserver covering stable combine behavior with dynamic queries: adding/removing/replacing queries, handling duplicate keys, and verifying combine re-invocation and result updates. |
Query Core Implementation packages/query-core/src/queriesObserver.ts |
Adds private field #lastQueryHashes to track query hashes; extends #combineResult to accept and compare query hashes; updates getOptimisticResult to compute and pass query hashes; triggers combine re-evaluation when query hashes change. |
React Query Tests packages/react-query/src/__tests__/useQueries.test.tsx |
Adds test case verifying useQueries returns correct results when query count changes with a stable combine reference. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- Focus areas:
- Query hash computation and comparison logic in
#combineResultto ensure correct change detection - Timing of
#lastQueryHashesupdates relative to#lastCombineand#lastResultassignments - Verify that combine is invoked only when necessary (hash changes) and with correct aggregated results
- Query hash computation and comparison logic in
Possibly related PRs
- TanStack/query#9886 — Also modifies QueriesObserver internals around queryHash handling and observer matching to address dynamic query changes.
- TanStack/query#9592 — Related updates to QueriesObserver to ensure memoized combine is re-run when queries or their results change.
- TanStack/query#9639 — Concurrent changes to QueriesObserver change-detection logic and observer reconfiguration in the same file.
Suggested labels
package: query-core, package: react-query
Suggested reviewers
- TkDodo
Poem
🐰 A stable combine, once stuck in time, Now dances with hashes in perfect rhyme, When queries shift left, or swap their place, The combined results show their true face! Hash-tracking magic, so swift and clean— Best observer fix the TanStack's seen! 🥬✨
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 (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The PR title accurately describes the main fix: addressing the issue where a stable combine reference wasn't updating correctly when queries changed dynamically. |
| Linked Issues check | ✅ Passed | The PR implementation fully addresses issue #8781 by tracking queryHashes in QueriesObserver to detect query changes before result updates, fixing the lag when using stable combine references. |
| Out of Scope Changes check | ✅ Passed | All changes are directly related to fixing the combine function lag issue: tracking queryHashes, updating combineResult signature, modifying getOptimisticResult, and adding comprehensive test coverage. |
| Description check | ✅ Passed | Pull request description includes all required sections: detailed problem statement, solution explanation, completed checklist items, and release impact confirmation with changeset. |
✨ 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
View your CI Pipeline Execution ↗ for commit 89db791104f4435887de45f9eb4ffdd07da0bdb9
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
✅ Succeeded | 5m 26s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 1m 24s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-12-07 18:34:00 UTC
More templates
- @tanstack/query-example-angular-auto-refetching
- @tanstack/query-example-angular-basic
- @tanstack/query-example-angular-basic-persister
- @tanstack/query-example-angular-devtools-panel
- @tanstack/query-example-angular-infinite-query-with-max-pages
- @tanstack/query-example-angular-optimistic-updates
- @tanstack/query-example-angular-pagination
- @tanstack/query-example-angular-query-options-from-a-service
- @tanstack/query-example-angular-router
- @tanstack/query-example-angular-rxjs
- @tanstack/query-example-angular-simple
- @tanstack/query-example-solid-astro
- @tanstack/query-example-solid-basic
- @tanstack/query-example-solid-basic-graphql-request
- @tanstack/query-example-solid-default-query-function
- @tanstack/query-example-solid-simple
- @tanstack/query-example-solid-start-streaming
- @tanstack/query-example-svelte-auto-refetching
- @tanstack/query-example-svelte-basic
- @tanstack/query-example-svelte-load-more-infinite-scroll
- @tanstack/query-example-svelte-optimistic-updates
- @tanstack/query-example-svelte-playground
- @tanstack/query-example-svelte-simple
- @tanstack/query-example-svelte-ssr
- @tanstack/query-example-svelte-star-wars
- @tanstack/query-example-vue-2.6-basic
- @tanstack/query-example-vue-2.7-basic
- @tanstack/query-example-vue-basic
- @tanstack/query-example-vue-dependent-queries
- @tanstack/query-example-vue-nuxt3
- @tanstack/query-example-vue-persister
- @tanstack/query-example-vue-simple
- @tanstack/query-example-react-algolia
- @tanstack/query-example-react-auto-refetching
- @tanstack/query-example-react-basic
- @tanstack/query-example-react-basic-graphql-request
- @tanstack/query-example-chat
- @tanstack/query-example-react-default-query-function
- @tanstack/query-example-react-devtools-panel
- @tanstack/query-example-eslint-legacy
- @tanstack/query-example-react-infinite-query-with-max-pages
- @tanstack/query-example-react-load-more-infinite-scroll
- @tanstack/query-example-react-nextjs
- @tanstack/query-example-react-nextjs-app-prefetching
- @tanstack/query-example-nextjs-suspense-streaming
- @tanstack/query-example-react-offline
- @tanstack/query-example-react-optimistic-updates-cache
- @tanstack/query-example-react-optimistic-updates-ui
- @tanstack/query-example-react-pagination
- @tanstack/query-example-react-playground
- @tanstack/query-example-react-prefetching
- @tanstack/query-example-react-react-native
- @tanstack/query-example-react-router
- @tanstack/query-example-react-rick-morty
- @tanstack/query-example-react-shadow-dom
- @tanstack/query-example-react-simple
- @tanstack/query-example-react-star-wars
- @tanstack/query-example-react-suspense
@tanstack/angular-query-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9954
@tanstack/eslint-plugin-query
npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9954
@tanstack/query-async-storage-persister
npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9954
@tanstack/query-broadcast-client-experimental
npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9954
@tanstack/query-core
npm i https://pkg.pr.new/@tanstack/query-core@9954
@tanstack/query-devtools
npm i https://pkg.pr.new/@tanstack/query-devtools@9954
@tanstack/query-persist-client-core
npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9954
@tanstack/query-sync-storage-persister
npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9954
@tanstack/react-query
npm i https://pkg.pr.new/@tanstack/react-query@9954
@tanstack/react-query-devtools
npm i https://pkg.pr.new/@tanstack/react-query-devtools@9954
@tanstack/react-query-next-experimental
npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9954
@tanstack/react-query-persist-client
npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9954
@tanstack/solid-query
npm i https://pkg.pr.new/@tanstack/solid-query@9954
@tanstack/solid-query-devtools
npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9954
@tanstack/solid-query-persist-client
npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9954
@tanstack/svelte-query
npm i https://pkg.pr.new/@tanstack/svelte-query@9954
@tanstack/svelte-query-devtools
npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9954
@tanstack/svelte-query-persist-client
npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9954
@tanstack/vue-query
npm i https://pkg.pr.new/@tanstack/vue-query@9954
@tanstack/vue-query-devtools
npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9954
commit: 89db791
Codecov Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 60.14%. Comparing base (f15b7fc) to head (89db791).
Additional details and impacted files
@@ Coverage Diff @@
## main #9954 +/- ##
===========================================
+ Coverage 45.89% 60.14% +14.25%
===========================================
Files 200 129 -71
Lines 8437 5661 -2776
Branches 1943 1555 -388
===========================================
- Hits 3872 3405 -467
+ Misses 4116 1952 -2164
+ Partials 449 304 -145
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.