useSWRMutation
Purpose and Scope
useSWRMutation is SWR’s remote-mutation hook for work that should happen only after an explicit user or program action. The ordinary useSWR hook starts from a key and a fetcher, then participates in SWR’s stale-while-revalidate lifecycle. useSWRMutation uses the same cache and key model, but it does not call the mutation fetcher during render or mount. Instead, it returns a trigger function that starts the remote operation, updates mutation-local state, can write through SWR’s mutate, and can optionally revalidate or populate the shared cache after the operation completes.
Sources: src/mutation/index.ts, src/mutation/types.ts, test/use-swr-remote-mutation.test.tsx
Use this hook when the application has a command-like operation such as submitting a form, saving settings, creating a record, or deleting a resource. Those operations still need SWR’s cache coordination, optimistic data, rollback behavior, and success or error callbacks, but they should not run just because a component rendered. The remote mutation tests demonstrate this distinction directly: the fetcher is not called on mount, and it is called only after a button invokes trigger. That behavior is the main practical difference between a data-fetching hook and this mutation hook.
Sources: test/use-swr-remote-mutation.test.tsx, src/mutation/index.ts
Relevant Source Files
src/mutation/index.ts- Implements the mutation hook factory, stores the latest key, fetcher, and configuration in refs, definestriggerandreset, uses SWR’s globalmutate, and applies the mutation middleware to the base SWR hook.src/mutation/types.ts- DefinesMutationFetcher,SWRMutationConfiguration, trigger overloads, and the public mutation response type that TypeScript users consume fromswr/mutation.mutation/package.json- Provides the subpackage shim for the mutation entrypoint, pointing CommonJS, ESM, and type consumers at the built mutation files underdist/mutation.test/use-swr-remote-mutation.test.tsx- Exercises runtime behavior such as trigger-driven execution, argument passing, success and error callbacks, thrown errors, and state updates.test/type/mutation.ts- Verifies the exportedTriggerWithoutArgstype and the inferred trigger shape for a simple string key mutation.test/type/trigger.ts- Verifies trigger argument inference, return types underthrowOnError, optional arguments, optimistic data typing, cache population typing, and the absence of confusingmutateandisValidatingfields from the mutation response.
Entrypoint and Hook Shape
Applications import the hook from swr/mutation, and the repository keeps that entrypoint separate from the core swr import. The local package shim declares built output locations for JavaScript module, JavaScript CommonJS, and TypeScript declarations, which lets tooling resolve the mutation submodule as its own package-facing surface. Runtime tests and type tests both import useSWRMutation from that subpath, so the documented entrypoint is not merely an internal file name; it is the public API shape used by consumers and validated in the repository.
Sources: mutation/package.json, test/use-swr-remote-mutation.test.tsx, test/type/mutation.ts, test/type/trigger.ts
The hook signature is organized around a key, a MutationFetcher, and an optional SWRMutationConfiguration. The fetcher receives the resolved key as its first argument and a readonly options object containing arg as its second argument. That second argument is how caller-supplied trigger data reaches the remote operation. For example, a mutation keyed by an API URL can receive a form payload, identifier, or command parameter from trigger, while the fetcher still receives the SWR key information needed to choose the endpoint or cache entry.
Sources: src/mutation/types.ts, test/use-swr-remote-mutation.test.tsx, test/type/trigger.ts
The response intentionally differs from the normal SWR response. Type tests assert that consumers get trigger, reset, data, and error, and also assert that the mutation response does not expose fields such as mutate or isValidating. The implementation comment explains the design reason: returning mutate from the mutation hook can be confusing because a user might call mutate when they actually mean to run the remote mutation with trigger. The hook therefore presents a smaller command-oriented API instead of the full read-hook response.
Sources: src/mutation/index.ts, test/type/trigger.ts
Execution Flow
Internally, useSWRMutation keeps the current key, fetcher, and configuration in refs. The hook updates those refs in an isomorphic layout effect, which allows the stable trigger callback to use the latest inputs without being recreated on every render. When trigger runs, it serializes the current key. If the fetcher is absent, it throws a missing-fetcher error. If the serialized key is empty, it throws a missing-key error. These checks prevent silent mutations that cannot be associated with a cache entry or cannot perform the remote work.
Sources: src/mutation/index.ts
After validation, trigger merges defaults, hook-level configuration, and per-trigger options. The implementation disables cache population by default with populateCache: false and defaults throwOnError to true. That means a remote mutation result updates the mutation hook’s local data state, but it does not automatically replace the shared SWR cache unless the caller opts into that behavior. The default is conservative: commands often return an acknowledgement, status object, or transformed resource that should not necessarily overwrite the query cache for the same key.
Sources: src/mutation/index.ts, src/mutation/types.ts
The hook then records a timestamp for the mutation and stores it in ditchMutationsUntilRef. This timestamp is the guard that prevents older or reset mutations from broadcasting stale state after a newer action. The hook sets isMutating to true, calls SWR’s mutate with the serialized key and the promise returned by the mutation fetcher, and forces throwOnError internally so it can catch failures and update local state. Successful results update data, clear error, set isMutating false, call onSuccess, and return the resolved data.
Sources: src/mutation/index.ts
Error handling follows the same timestamp guard. If the failed mutation is still current, the hook stores the error, sets isMutating false, calls onError, and then rethrows when the effective throwOnError option is true. Runtime tests cover a fetcher that throws asynchronously, assert that the rendered error state appears, and also show callers catching the rejected trigger promise inline. This dual behavior is important: components can render mutation errors from hook state, while event handlers can still use promise control flow for local branching or toast notifications.
Sources: src/mutation/index.ts, test/use-swr-remote-mutation.test.tsx
reset is the companion to trigger. It advances the ditch timestamp and restores the local mutation state to undefined data, undefined error, and not mutating. Advancing the timestamp matters because a reset during an in-flight request should not allow the older request to repaint the component when it later resolves. In practice, this makes reset useful for closing dialogs, clearing form-submit feedback, or abandoning a previous command when the user starts a new interaction.
Sources: src/mutation/index.ts, test/type/trigger.ts
Fetcher Arguments and Type Inference
MutationFetcher is generic over returned data, the SWR key, and an extra argument type. If the key is a function returning a usable key, the fetcher receives the function’s resolved argument. If the key is null, undefined, or false, the fetcher type becomes never, reflecting that there is no valid remote operation to run. Otherwise, the fetcher receives the key value itself. In all valid cases, the second parameter is a readonly object with arg, making trigger payloads explicit and distinct from cache keys.
Sources: src/mutation/types.ts
The tests show how this works for real calls. A hook created with an array key such as a generated key plus an initial argument receives that entire resolved array as the fetcher’s first parameter. When the component calls trigger('arg1'), the fetcher receives { arg: 'arg1' } as the second parameter. Type tests also confirm that a string key is inferred as a string in the fetcher and that a fetcher annotated with { arg: number } requires trigger to receive a number. This gives mutation code strong coupling between event payloads and remote-command parameters without hiding the SWR key.
Sources: test/use-swr-remote-mutation.test.tsx, test/type/trigger.ts
Trigger overloads refine the returned promise. When the extra argument is required, TriggerWithArgs requires the first argument. When the extra argument is optional, TriggerWithOptionsArgs allows it to be omitted. When no argument is expected, TriggerWithoutArgs accepts no meaningful payload and uses null or omission. The overloads also account for throwOnError: calls with throwOnError: false can resolve to data or undefined, while calls that throw on error can be typed as resolving to non-undefined data after success.
Sources: src/mutation/types.ts, test/type/mutation.ts, test/type/trigger.ts
Configuration Reference
SWRMutationConfiguration includes cache and lifecycle controls familiar from SWR mutation flows. revalidate can be a boolean or a function of the returned data and key arguments. populateCache can be a boolean or a transformer from the mutation result and current cached data to the new cached data. optimisticData can be a direct value or a function of current cached data. rollbackOnError can be a boolean or a predicate. The configuration can also provide a mutation-specific fetcher and onSuccess or onError callbacks.
Sources: src/mutation/types.ts
The same configuration shape can be supplied at hook creation or at trigger time. The implementation merges defaults, then hook-level configuration, then per-trigger options, so an event handler can override behavior for a particular submit. Runtime tests cover both an onSuccess passed to the hook and an onSuccess passed to trigger. This is useful when most invocations share defaults but one button or form submission needs different callback behavior, cache population, rollback policy, or error throwing semantics.
Sources: src/mutation/index.ts, test/use-swr-remote-mutation.test.tsx
A compact mental model is: use the key to identify the SWR cache entry, use the fetcher to perform the remote command, use trigger to pass the command payload, and use configuration to decide how the command interacts with cached read data. If the mutation response should become the cached data, opt into populateCache or provide a transformer. If the UI should show a predicted value immediately, provide optimisticData and decide whether failed operations should roll back through rollbackOnError. If the surrounding code should not catch rejected promises, set throwOnError false and handle error state instead.
Sources: src/mutation/types.ts, src/mutation/index.ts, test/type/trigger.ts
Example Pattern
import useSWRMutation from 'swr/mutation'
async function updateUser(url: string, { arg }: { arg: { name: string } }) {
const response = await fetch(url, {
method: 'PATCH',
body: JSON.stringify(arg)
})
if (!response.ok) throw new Error('failed to update user')
return response.json() as Promise<{ name: string }>
}
function SaveNameButton() {
const { trigger, data, error, isMutating, reset } = useSWRMutation(
'/api/user',
updateUser,
{ populateCache: true }
)
return (
<button
disabled={isMutating}
onClick={() => trigger({ name: 'Ada' })}
onBlur={reset}
>
{error ? 'Retry' : data ? `Saved ${data.name}` : 'Save'}
</button>
)
}This pattern mirrors the repository tests: nothing happens on mount, the button calls trigger, the fetcher receives the key and { arg }, and the component reacts to mutation-local state. The example opts into cache population because the remote result is the same shape as the read data for the key. If the server returned only a status flag, keeping the default populateCache: false would be safer, and a separate read hook could revalidate after the command.
Sources: test/use-swr-remote-mutation.test.tsx, src/mutation/index.ts, src/mutation/types.ts
Testing Signals and Next Steps
The runtime suite is the best guide for behavioral expectations. It verifies returned data after triggering, returned promise data from trigger, the exact fetcher argument signature, success callbacks, trigger-level callback overrides, and error handling that both sets hook state and rejects the trigger promise. The type suites add constraints that runtime tests cannot express, including the exact TriggerWithoutArgs inference for simple keys, required and optional argument behavior, throwOnError return narrowing, and typed optimistic or cache-population callbacks that can refer to existing SWR data.
Sources: test/use-swr-remote-mutation.test.tsx, test/type/mutation.ts, test/type/trigger.ts
For related reading, pair this page with the broader mutation concepts page when designing optimistic UI, the mutate reference when coordinating shared cache writes, and the TypeScript types page when authoring reusable domain-specific mutation hooks. In application code, start by choosing a stable key and a fetcher signature, then decide whether the mutation result should populate cache, whether optimistic data is appropriate, and whether callers should catch promise rejections or read error state from the hook.