diff --git a/packages/query-core/src/__tests__/mutationObserver.test.tsx b/packages/query-core/src/__tests__/mutationObserver.test.tsx index 7c72e21e40..a339d91c83 100644 --- a/packages/query-core/src/__tests__/mutationObserver.test.tsx +++ b/packages/query-core/src/__tests__/mutationObserver.test.tsx @@ -499,4 +499,54 @@ describe('mutationObserver', () => { unsubscribe() }) + + it('should track an in-flight mutation again after unsubscribing and resubscribing', async () => { + const mutationObserver = new MutationObserver(queryClient, { + mutationFn: (text: string) => sleep(20).then(() => text), + }) + + const unsubscribe = mutationObserver.subscribe(() => undefined) + mutationObserver.mutate('input') + await vi.advanceTimersByTimeAsync(0) + + // React tears down and re-establishes subscriptions while keeping the + // component state (StrictMode, , re-suspending boundaries) + unsubscribe() + const subscriptionHandler = vi.fn() + mutationObserver.subscribe(subscriptionHandler) + + await vi.advanceTimersByTimeAsync(20) + + expect(mutationObserver.getCurrentResult()).toMatchObject({ + status: 'success', + data: 'input', + }) + expect(subscriptionHandler).toHaveBeenCalledTimes(1) + expect(subscriptionHandler).toHaveBeenCalledWith( + expect.objectContaining({ status: 'success', data: 'input' }), + ) + }) + + it('should report the final state of a mutation that settled while unsubscribed', async () => { + const mutationObserver = new MutationObserver(queryClient, { + mutationFn: (text: string) => sleep(20).then(() => text), + }) + + const unsubscribe = mutationObserver.subscribe(() => undefined) + mutationObserver.mutate('input') + await vi.advanceTimersByTimeAsync(0) + + unsubscribe() + + // mutation settles while no one is subscribed + await vi.advanceTimersByTimeAsync(20) + expect(mutationObserver.getCurrentResult().status).toBe('pending') + + mutationObserver.subscribe(() => undefined) + + expect(mutationObserver.getCurrentResult()).toMatchObject({ + status: 'success', + data: 'input', + }) + }) }) diff --git a/packages/query-core/src/mutationObserver.ts b/packages/query-core/src/mutationObserver.ts index 21523963ce..78d4810078 100644 --- a/packages/query-core/src/mutationObserver.ts +++ b/packages/query-core/src/mutationObserver.ts @@ -93,6 +93,16 @@ export class MutationObserver< } } + protected onSubscribe(): void { + if (this.listeners.size === 1 && this.#currentMutation) { + // re-attach to the mutation the first listener unsubscribing detached us + // from, and refresh the result in case the mutation settled while we + // were not watching it + this.#currentMutation.addObserver(this) + this.#updateResult() + } + } + protected onUnsubscribe(): void { if (!this.hasListeners()) { this.#currentMutation?.removeObserver(this)