Describe the bug
When a per-call onSuccess or onError callback starts another mutation using the same useMutation instance, the second mutation's onSettled callback receives the first mutation's result and variables. It runs again with its own result when the second mutation finishes.
The reproduction logs only the callback registered for the second mutation:
[
{ "data": "result-1", "error": null, "variables": 1 },
{ "data": "result-2", "error": null, "variables": 2 }
]
Your minimal, reproducible example
https://yjdm6m.csb.app/ https://codesandbox.io/s/yjdm6m
Steps to reproduce
- Open the reproduction and click
1. Start second mutation in onSuccess.
- Observe that the second mutation's callback receives both
result-1 / variables 1 and result-2 / variables 2.
- Click
2. Start second mutation in onError. The second mutation's callback similarly receives the first mutation's error and variables before receiving its own successful result.
- As a control, click
3. Sequential calls outside callbacks. This correctly logs only the second mutation's result once.
- Repeat with StrictMode enabled using the link at the bottom. The behavior is the same.
Expected behavior
The second mutation's onSettled callback should run only after that mutation settles, with its own data, error, and variables:
[
{ "data": "result-2", "error": null, "variables": 2 }
]
This is not a request to invoke per-call callbacks for every consecutive mutation. The issue is that the latest mutation's callback receives a different mutation's result.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- macOS 26.5
- Chromium 150.0.0.0, as reported by the browser's user agent
- React / React DOM 19.2.1
- Reproduces with StrictMode both enabled and disabled
Tanstack Query adapter
react-query
TanStack Query version
@tanstack/react-query@5.102.8 and @tanstack/query-core@5.102.8
TypeScript version
No response
Additional context
In additional local checks, the second mutation was held pending using a manually resolved Promise. Its onSettled callback still received the first mutation's result before the second mutation finished.
Omitting the second call's optional options argument also produces an internal TypeError, even though the user callback does not throw:
mutation.mutate(1, {
onSuccess: () => {
mutation.mutate(2)
},
})
TypeError: Cannot read properties of undefined (reading 'onSettled')
In MutationObserver.#notify, this.#mutateOptions.onSettled is read after invoking onSuccess or onError. The nested mutate call replaces #mutateOptions, while the action's result and variables still belong to the first mutation.
The pending-Promise and omitted-options cases were checked separately locally; the shared sandbox contains the three button scenarios described above.
Codex assisted with preparing and verifying the reproduction.
Describe the bug
When a per-call
onSuccessoronErrorcallback starts another mutation using the sameuseMutationinstance, the second mutation'sonSettledcallback receives the first mutation's result and variables. It runs again with its own result when the second mutation finishes.The reproduction logs only the callback registered for the second mutation:
[ { "data": "result-1", "error": null, "variables": 1 }, { "data": "result-2", "error": null, "variables": 2 } ]Your minimal, reproducible example
https://yjdm6m.csb.app/ https://codesandbox.io/s/yjdm6m
Steps to reproduce
1. Start second mutation in onSuccess.result-1/ variables1andresult-2/ variables2.2. Start second mutation in onError. The second mutation's callback similarly receives the first mutation's error and variables before receiving its own successful result.3. Sequential calls outside callbacks. This correctly logs only the second mutation's result once.Expected behavior
The second mutation's
onSettledcallback should run only after that mutation settles, with its own data, error, and variables:[ { "data": "result-2", "error": null, "variables": 2 } ]This is not a request to invoke per-call callbacks for every consecutive mutation. The issue is that the latest mutation's callback receives a different mutation's result.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
Tanstack Query adapter
react-query
TanStack Query version
@tanstack/react-query@5.102.8and@tanstack/query-core@5.102.8TypeScript version
No response
Additional context
In additional local checks, the second mutation was held pending using a manually resolved Promise. Its
onSettledcallback still received the first mutation's result before the second mutation finished.Omitting the second call's optional options argument also produces an internal TypeError, even though the user callback does not throw:
In
MutationObserver.#notify,this.#mutateOptions.onSettledis read after invokingonSuccessoronError. The nestedmutatecall replaces#mutateOptions, while the action's result and variables still belong to the first mutation.The pending-Promise and omitted-options cases were checked separately locally; the shared sandbox contains the three button scenarios described above.
Codex assisted with preparing and verifying the reproduction.