Skip to content

fix(react-query): recompute useMutationState when the filters change - #11329

Open
MahathirMohammadShuvo wants to merge 1 commit into
TanStack:mainfrom
MahathirMohammadShuvo:fix-11272-usemutationstate-filters
Open

fix(react-query): recompute useMutationState when the filters change#11329
MahathirMohammadShuvo wants to merge 1 commit into
TanStack:mainfrom
MahathirMohammadShuvo:fix-11272-usemutationstate-filters

Conversation

@MahathirMohammadShuvo

@MahathirMohammadShuvo MahathirMohammadShuvo commented Aug 29, 2026

Copy link
Copy Markdown

Fixes #11272

useMutationState ignored changes to its options. After the first render
result.current was only rewritten from inside the mutationCache.subscribe()
callback, and getSnapshot returns that ref, so a render carrying new filters or
a new select recomputed nothing. The hook kept serving the previous options'
result until something unrelated touched the cache — and if nothing did, it stayed
wrong indefinitely.

useIsMutating is built on this hook and went stale in the same way, which is the
wider half of the bug: switching its filters left the count reporting the old
filter's mutations.

The fix

The result is recomputed on render as well.

useIsFetching does the same work directly inside getSnapshot, which it can
afford because it returns a number. This hook returns an array, and a fresh array
per call never satisfies React's snapshot consistency check — I tried it, and it
logs "The result of getSnapshot should be cached to avoid an infinite loop" and
then throws Maximum update depth exceeded. So the array stays in a ref.

Recomputing unconditionally would then re-run select on every unrelated render,
which QueryObserver already avoids by memoising on options.select === this.#selectFn.
The recompute is guarded the same way — replaceEqualDeep on filters, identity on
select — so an unrelated render costs nothing, and inline options behave exactly as
they do in useQuery today.

Tests

Six added to useMutationState.test.tsx. Four fail on main because the value never
updates: filters change, select changes, filters stop matching, and useIsMutating
with changing filters. The other two are regression guards, and each fails against a
specific defect rather than passing trivially — removing replaceEqualDeep fails the
reference-stability test, and removing the guard fails "should not re-run a stable
select on an unrelated render".

pnpm test:lib on @tanstack/react-query: 35 files, 577 tests passing.

Other adapters

preact-query's useMutationState is a line-for-line copy and has the identical
bug; svelte-query and solid-query share the shape. vue-query already fixes it,
via a watch on the resolved options.

Left out of this PR deliberately, following how the repo split the same situation
recently — #11130 fixed useQueries in react and #11315 did preact separately, with
its own changeset — and the same for #11305. Happy to follow up on the others once
the approach here is settled.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed useMutationState so results update when filters or selection logic change.
    • Fixed stale useIsMutating values when its filters change.
    • Improved result stability during unrelated component re-renders.
  • Tests

    • Added coverage for changing filters, selection logic, matching behavior, result stability, and mutation counts.

After the first render, `result.current` was only rewritten from inside the
`mutationCache.subscribe()` callback, and `getSnapshot` returns that ref, so a
render carrying new `filters` or a new `select` recomputed nothing. The hook kept
serving the previous options' result until something unrelated touched the cache,
and if nothing did, it stayed wrong indefinitely. `useIsMutating` is built on this
hook and went stale with it.

It is now recomputed on render as well. `useIsFetching` does the same work inside
`getSnapshot`, which it can afford because it returns a number; this hook returns an
array, and a fresh one per call never satisfies the snapshot consistency check. So
the array stays in a ref, and the recompute is guarded on the options the same way
`QueryObserver` guards `select`, which keeps an unrelated render from re-running it.

Fixes TanStack#11272
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 96943176-a2a0-4b58-93a5-31f0d5dc271b

📥 Commits

Reviewing files that changed from the base of the PR and between 2969edf and a83fc93.

📒 Files selected for processing (3)
  • .changeset/tidy-carrots-shake.md
  • packages/react-query/src/__tests__/useMutationState.test.tsx
  • packages/react-query/src/useMutationState.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

useMutationState now recomputes when filters or select change during render. Tests cover filter changes, selector changes, result stability, and useIsMutating. A patch changeset documents the fix.

Changes

Mutation state updates

Layer / File(s) Summary
Recompute changed mutation options
packages/react-query/src/useMutationState.ts
useMutationState tracks filters and select changes, recomputes results during render, and preserves result references with replaceEqualDeep.
Validate option-driven updates
packages/react-query/src/__tests__/useMutationState.test.tsx, .changeset/tidy-carrots-shake.md
Tests cover filter changes, selector changes, empty matches, referential stability, selector call counts, and useIsMutating. The changeset declares a patch release.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to a83fc

This change makes mutation state and mutation counts update when filters or selectors change, correcting stale results without changing the public API or deployment behavior. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 2 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix: recomputing useMutationState when filters change. It does not mention select changes, but that detail is secondary.
Description check ✅ Passed The description provides a detailed explanation of the bug, the fix, test coverage, test results, and deliberate adapter scope. It omits the template headings and unchecked checklist, but the required…
Linked Issues check ✅ Passed The implementation satisfies issue #11272 by recomputing useMutationState when filters change without mutation-cache notifications. The tests cover the reported behavior and related useIsMutating beha…
Out of Scope Changes check ✅ Passed The changeset, production fix, and tests are directly related to issue #11272. The select and useIsMutating coverage supports the same stale-result defect, and no unrelated adapter changes were includ…
Full details: Description check

Explanation

The description provides a detailed explanation of the bug, the fix, test coverage, test results, and deliberate adapter scope. It omits the template headings and unchecked checklist, but the required technical information is present.

Full details: Linked Issues check

Explanation

The implementation satisfies issue #11272 by recomputing useMutationState when filters change without mutation-cache notifications. The tests cover the reported behavior and related useIsMutating behavior.

Full details: Out of Scope Changes check

Explanation

The changeset, production fix, and tests are directly related to issue #11272. The select and useIsMutating coverage supports the same stale-result defect, and no unrelated adapter changes were included.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useMutationState does not update when filters change (react-query)

1 participant