Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/khaki-hoops-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-core': patch
---

Reattach `MutationObserver` to its current mutation when a listener subscribes again, so a `useMutation` result no longer stays `pending` after React tears down and re-establishes the subscription mid-mutation.
42 changes: 42 additions & 0 deletions packages/query-core/src/__tests__/mutationObserver.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,48 @@ describe('mutationObserver', () => {
expect(queryClient.getMutationCache().findAll()).toHaveLength(0)
})

it('resubscribing should reattach the observer to the in-flight mutation', async () => {
const mutation = new MutationObserver(queryClient, {
mutationFn: (text: string) => sleep(20).then(() => text),
})

const unsubscribe = mutation.subscribe(vi.fn())

mutation.mutate('input')

unsubscribe()

const subscriptionHandler = vi.fn()
mutation.subscribe(subscriptionHandler)

await vi.advanceTimersByTimeAsync(20)
expect(mutation.getCurrentResult()).toMatchObject({
status: 'success',
data: 'input',
})
expect(subscriptionHandler).toHaveBeenCalledTimes(1)
})

it('resubscribing should pick up a mutation that settled while unsubscribed', async () => {
const mutation = new MutationObserver(queryClient, {
mutationFn: (text: string) => sleep(20).then(() => text),
})

const unsubscribe = mutation.subscribe(vi.fn())

mutation.mutate('input')

unsubscribe()

await vi.advanceTimersByTimeAsync(20)
mutation.subscribe(vi.fn())

expect(mutation.getCurrentResult()).toMatchObject({
status: 'success',
data: 'input',
})
})

it('reset should remove observer to trigger GC', async () => {
const mutation = new MutationObserver(queryClient, {
mutationFn: (text: string) => sleep(5).then(() => text),
Expand Down
8 changes: 8 additions & 0 deletions packages/query-core/src/mutationObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ export class MutationObserver<
}
}

protected onSubscribe(): void {
if (this.listeners.size === 1 && this.#currentMutation) {
this.#currentMutation.addObserver(this)

this.#updateResult()
}
}

protected onUnsubscribe(): void {
if (!this.hasListeners()) {
this.#currentMutation?.removeObserver(this)
Expand Down