Skip to content

fix(solid-query): untrack the result read in useMutationState's cache subscription - #11359

Open
daviduzumeri wants to merge 1 commit into
TanStack:mainfrom
daviduzumeri:fix/solid-query-use-mutation-state-loop
Open

fix(solid-query): untrack the result read in useMutationState's cache subscription#11359
daviduzumeri wants to merge 1 commit into
TanStack:mainfrom
daviduzumeri:fix/solid-query-use-mutation-state-loop

Conversation

@daviduzumeri

@daviduzumeri daviduzumeri commented Sep 1, 2026

Copy link
Copy Markdown

🎯 Changes

useMutationState's mutation-cache subscription reads the hook's own result signal:

const unsubscribe = mutationCache().subscribe(() => {
  const nextResult = replaceEqualDeep(result(), getResult(mutationCache(), options()))
  if (result() !== nextResult) setResult(nextResult)
})

MutationCache notifies synchronously, so when a mutation is started from inside a Solid computation, that callback runs while the computation is still the active listener. The result() read therefore registers the signal as a dependency of that computation — and setResult immediately invalidates it.

The practical effect: calling mutate from inside a createEffect re-runs the effect on every mutation, and an unguarded mutate loops. It also attaches a spurious dependency to whatever computation happens to be running when any mutation settles, which is hard to attribute when it shows up as a runaway effect cascade in production.

The read is now untracked. Behaviour is otherwise identical.

Regression test

Added to useMutationState.test.tsx: an effect that triggers one mutation, with a guard so a genuine loop still terminates.

  • before: expected 2 to be 1 — the effect re-ran
  • after: passes

Relationship to #9644

#9644 proposed the same untrack alongside a useIsMutating fix. That PR has been open since Sept 2025, last updated June 2026, and is now conflicted; its useIsMutating half has since landed separately. This is just the remaining half, rebased on main with a test and a changeset. Happy to close it in favour of #9644 if that one is revived.

Also related: #10907 (the enabled: false SSR hang) was a different symptom of solid-query reading signals from cache callbacks, and is already fixed.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

Scope of what I ran: vitest run for @tanstack/solid-query247 runtime tests pass (13 files). useQuery.test-d.tsx fails 5 type assertions, but it does so identically on unmodified main with my changes stashed, so it is pre-existing and unrelated to this change.

🚀 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

    • Prevented mutation-triggered computations from rerunning synchronously when their own mutation state changes.
    • Improved reactive behavior for useMutationState when mutations are initiated within reactive effects.
  • Tests

    • Added coverage confirming effects run only once when triggering a mutation through useMutationState.

… subscription

MutationCache notifies synchronously, so the subscription callback runs while
the computation that called `mutate` is still the active listener. Reading the
hook's own result signal there registered it as a dependency of that
computation, which `setResult` then immediately invalidated — so calling
`mutate` from inside an effect re-ran the effect on every mutation, and an
unguarded `mutate` looped.

Adds a regression test: an effect that mutates once runs twice before this
change and once after.
@coderabbitai

coderabbitai Bot commented Sep 1, 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: Team

Run ID: 87c13d4c-1e8e-48cc-a463-91e752e50bf6

📥 Commits

Reviewing files that changed from the base of the PR and between f6ae1cc and 8b985bb.

📒 Files selected for processing (3)
  • .changeset/solid-query-mutation-state-untrack.md
  • packages/solid-query/src/__tests__/useMutationState.test.tsx
  • packages/solid-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 reads its result signal with untrack during mutation-cache updates. A regression test verifies that mutations started in createEffect do not synchronously rerun the effect. A patch changeset documents the fix.

Changes

Mutation state tracking

Layer / File(s) Summary
Untrack mutation result reads
packages/solid-query/src/useMutationState.ts
useMutationState uses untrack(result) for the previous result before structural sharing and comparison.
Regression coverage and release note
packages/solid-query/src/__tests__/useMutationState.test.tsx, .changeset/solid-query-mutation-state-untrack.md
The test verifies that an effect-triggered mutation runs the effect once. The changeset records the patch release note.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 8b985

This change prevents mutation-cache updates from spuriously rerunning Solid computations while preserving existing result behavior, with regression coverage for the reported loop. No actionable merge-blocking risk remains.

🚥 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 2 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 and concisely identifies the main change: untracking the result read in the Solid Query mutation-state cache subscription.
Description check ✅ Passed The description includes all required template sections, explains the motivation and implementation, documents the regression test and test results, and includes the changeset release impact.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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 2 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.

1 participant