fix(query-core): re-attach MutationObserver to its in-flight mutation on resubscribe by iamshahid1997 · Pull Request #11217 · TanStack/query · GitHub
Skip to content

fix(query-core): re-attach MutationObserver to its in-flight mutation on resubscribe - #11217

Closed
iamshahid1997 wants to merge 1 commit into
TanStack:mainfrom
iamshahid1997:fix/mutation-observer-resubscribe
Closed

iamshahid1997 wants to merge 1 commit into
TanStack:mainfrom
iamshahid1997:fix/mutation-observer-resubscribe

Conversation

@iamshahid1997

@iamshahid1997 iamshahid1997 commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11171

Problem

When the last listener unsubscribes, MutationObserver.onUnsubscribe() removes the observer from its current mutation — but resubscribing never adds it back. React legitimately tears down and re-establishes the store subscription of a mounted component that keeps its state (<Activity mode="hidden">, re-suspending <Suspense> boundaries, StrictMode in dev).

If that happens while a mutation is in flight, the mutation settles against an empty observer list, and the observer keeps serving its frozen pending snapshot from getCurrentResult() forever:

  • isPending stays true → submit buttons using disabled={isPending} stay disabled until remount
  • callbacks passed to mutate(vars, { onSuccess }) never fire
  • devtools/MutationCache show success while the hook shows pending

Fix

Mirror QueryObserver.onSubscribe(): when the first listener subscribes, re-attach the observer to its current mutation (Mutation.addObserver is already idempotent) and refresh the result snapshot in case the mutation settled while detached.

Tests

Two regression tests in mutationObserver.test.tsx, driving subscribe/unsubscribe the way useSyncExternalStore does:

  1. unsubscribe + resubscribe while the mutation is in flight → observer reaches success and notifies the new listener
  2. mutation settles while fully unsubscribed → resubscribing refreshes the snapshot to the final state

Both fail on main and pass with the fix. Full query-core (627) and react-query (577) suites pass.

Summary by CodeRabbit

  • Bug Fixes

    • Mutation status and results now refresh correctly when listeners unsubscribe and later resubscribe.
    • In-progress mutations remain tracked across subscription changes.
    • Mutations that finish while no listeners are present now report their final success state when observed again.
  • Tests

    • Added coverage for mutation observer behavior across unsubscribe and resubscribe cycles.

… on resubscribe

When the last listener unsubscribes, onUnsubscribe() removes the observer
from its current mutation, but resubscribing never added it back. If React
tore down and re-established the subscription while a mutation was in
flight (StrictMode, <Activity> hide/show, re-suspending boundaries), the
mutation settled against an empty observer list and the observer kept
serving its frozen 'pending' snapshot forever.

Mirror QueryObserver.onSubscribe(): when the first listener subscribes,
re-attach to the current mutation and refresh the result in case the
mutation settled while detached.

Fixes TanStack#11171
@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

@TkDodo

TkDodo commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

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.

useMutation freezes at pending forever when React re-subscribes during an in-flight mutation (<Activity> hide/show)

2 participants