The pattern
A form dialog runs an async save. While the save is in flight, the submit button shows pending. On success, the dialog closes with the response. On failure, the dialog stays open so the user can retry without losing what they typed.
That is the entire shape of useMutationFlow. It lives in a
subpath import (react-call/mutation-flow) so consumers who
don't need it pay zero bytes.
Where each piece lives
- MutationFn is a prop on the Call. It runs the side
effect and decides — by calling
call.end(…)or not — whether the Call closes. It lives in caller scope, alongside your domain logic. - useMutationFlow lives in the Callable body. It wraps the MutationFn, tracks pending, and returns a Trigger you wire to your submit button.
- Pending is local component state inside the Callable.
Disable inputs against
trigger.pendingthe same way you'd disable against any other piece of state.
Failure semantics
When the MutationFn throws, useMutationFlow clears pending — via the same
.finally that tracks the in-flight state — and does nothing
to the error itself: it propagates as an unhandled rejection. The lib
doesn't own your error handling, so it won't silence, log, or re-route
the throw. If the failure matters to your UX or telemetry, wrap the body
of your MutationFn in try/catch.
The Call stays open not because the throw was caught, but because
closing a Call requires an explicit call.end() — and a
MutationFn that throws never reaches it. The user sees the dialog return to
its idle state and can try again.