mutate
Purpose and Scope
mutate is SWR's imperative escape hatch for changing cached data and asking subscribed hooks to revalidate outside the normal fetch-on-render lifecycle. Most SWR usage starts with useSWR(key, fetcher), where the hook reads cached data, starts a request, and rerenders as data changes. mutate handles the complementary cases: a user clicks a button that changes data, a local piece of shared state needs to be updated, an optimistic UI should appear before the server confirms a write, or a component wants to programmatically refresh a key without waiting for focus, reconnect, or interval revalidation.
Sources: src/_internal/types.ts, test/use-swr-local-mutation.test.tsx
There are two reader-facing forms to keep distinct. The scoped or global mutator is obtained from useSWRConfig().mutate or imported as mutate from swr, and it accepts a key argument because it can address any cache entry in the current provider scope. The bound mutator is returned from useSWR(...) and already knows the hook's key, so callers pass data, an updater callback, a promise, or options without repeating the key. The local mutation tests exercise the scoped form through useSWRConfig().mutate, while the type tests exercise the bound form through const { mutate } = useSWR<string>('').
Sources: test/use-swr-local-mutation.test.tsx, test/type/mutate.ts
Relevant Source Files
src/_internal/types.tsdefines the internalGlobalStatetuple, including mutation timestamps and the scoped mutator slot, plus public fetcher and cache-related types that explain how SWR models asynchronous data.test/use-swr-local-mutation.test.tsxverifies observable local mutation behavior: programmatic revalidation, cache writes, deduped refetches, local state sharing with no fetcher, and interaction withuseSWRConfig().mutate.test/use-swr-remote-mutation.test.tsxverifies the separateuseSWRMutationtrigger model, including trigger return values, argument passing, and success or error callbacks; it is useful for distinguishing cache mutation from remote mutation hooks.test/type/mutate.tsvalidates the TypeScript contract for mutators, including bound mutation data typing, key-filter mutation, generic result inference,populateCacheeffects, and callback argument types.
Core Concepts
A mutation in SWR is not just a write to a JavaScript object. The internal state model includes event revalidators, mutation timestamps, fetch cache, preload cache, a scoped mutator, cache setter, and cache subscriber. That shape explains why a mutation can notify active hooks, coordinate with revalidation, and avoid letting stale network responses incorrectly overwrite newer local updates. The tests do not expose every implementation branch, but they do show the public invariant: when a key is mutated, components that read that key observe a consistent sequence of cache update and revalidation results.
Sources: src/_internal/types.ts, test/use-swr-local-mutation.test.tsx
The simplest mutate(key) call means “revalidate this cache entry now.” In the local mutation tests, a component fetches a numeric value and then calls the scoped mutate with only the key. The displayed value advances from the mounted value to the next fetched value. A separate test repeats that behavior inside a nonzero dedupingInterval, showing that explicit mutation-driven revalidation is not merely another passive deduped render; it is a deliberate refresh request by the caller. This is the form to use when the cache may be stale but the UI does not need an immediate optimistic value.
Sources: test/use-swr-local-mutation.test.tsx
Passing data to mutate adds a local cache write before or alongside revalidation. In the tests, mutate(key, 'mutate') is described as “mutate and revalidate,” and the component ultimately observes the revalidated fetcher result. This matters for optimistic user interfaces: the UI can become responsive immediately, then SWR asks the source of truth for the final value. The official optimistic UI example describes the same user-facing pattern: mutate cached data immediately, then trigger revalidation with the API. The test evidence anchors the behavior in SWR's cache and rerender machinery rather than in one example app.
Sources: test/use-swr-local-mutation.test.tsx
mutate can also be used as a small local state bus when no fetcher is supplied. A local test defines a custom useSharedState helper around useSWR(key, { fallbackData }), returns the hook's bound mutate as a setter, and updates two independent keys from a click handler. The rendered text changes from the fallback values to the new values without a remote fetcher. This is not the primary data-fetching story from the README, but it is an intentional supported pattern validated by tests: SWR cache entries can hold local state shared by components in the same provider scope.
Sources: test/use-swr-local-mutation.test.tsx
API Reference
| Entry point | Typical form | What it targets | Return shape validated by types |
|---|---|---|---|
| Scoped/global mutator | mutate(key, dataOrUpdater?, options?) | Any key in the current SWR cache provider scope | A promise for one result, or an array when a key filter matches multiple entries |
| Bound mutator | const { mutate } = useSWR<Data>(key) then mutate(dataOrUpdater?, options?) | The key used by that hook | A promise resolving to the hook data type or undefined |
| Key-filter mutation | mutate(predicate, updater) | All cache keys whose serialized arguments satisfy the predicate | A promise for an array of per-key mutation results |
| Remote mutation hook | const { trigger } = useSWRMutation(key, fetcher) | A request operation initiated by a trigger | trigger returns the remote mutation result and can update mutation state |
The TypeScript tests make the return contract more precise than prose examples usually can. A bound mutate from useSWR<string>('') accepts an asynchronous updater that resolves to a string, and rejects an asynchronous updater that resolves to a number. The rejection is intentional even when populateCache: false is provided, because the mutator remains typed to the data domain of that hook. Scoped mutate can be parameterized with a generic, such as mutate<number>(predicate, updater), so key-filtered updates infer an array of number | undefined results rather than losing all type information.
Sources: test/type/mutate.ts
The key-filter overload is especially important for cache-wide invalidation. The type test passes a predicate that receives Arguments, checks for string keys beginning with a prefix, and returns an updater whose input is number | undefined. That shape documents two practical rules. First, predicates operate on SWR key arguments, not on component instances. Second, updater callbacks must handle missing cached data because a matching key may not have a current value. In application code, that usually means writing idempotent updater functions that can create data from undefined or skip entries they do not understand.
Sources: test/type/mutate.ts
Local Mutation Flow
A local mutation flow begins with a mounted hook subscribing to a key. When a caller invokes the scoped mutator with that key, SWR updates internal mutation bookkeeping, writes through the cache setter when data is supplied, and notifies subscribers so active hooks can rerender. If revalidation is enabled, SWR also schedules a fetcher call and reconciles the final response back into the cache. The local tests demonstrate this flow through visible DOM transitions: initial hydration has no data, mount resolves the first fetch, mutation is invoked inside act, and the component eventually renders the post-mutation or post-revalidation value.
Sources: src/_internal/types.ts, test/use-swr-local-mutation.test.tsx
Request deduplication remains part of the flow. One local test mounts two useSWR hooks on the same key and then mutates the key while using a nonzero dedupingInterval. The test expects the rendered value to advance once, not to multiply network work for each subscriber. For readers building shared dashboards or layouts with repeated data dependencies, this is a useful operational guarantee: mutation is broadcast to interested hooks, but SWR still treats the key as the unit of request coordination. You should mutate keys, not individual component copies of data.
Sources: test/use-swr-local-mutation.test.tsx
A practical optimistic update sequence is therefore: compute the new local value, call the bound or scoped mutator with that value or with a callback from previous data, and allow revalidation to confirm the remote state. When the updater depends on the previous cache value, prefer a callback because the type tests show that SWR models callback input as possibly undefined. When the update is local-only, such as shared UI state with no fetcher, use fallbackData to establish the initial value and call the bound mutator like a setter. When the update represents a server write, compare this API with useSWRMutation, which separates the remote trigger from render-time fetching.
Sources: test/use-swr-local-mutation.test.tsx, test/use-swr-remote-mutation.test.tsx, test/type/mutate.ts
Remote Mutation and useSWRMutation Relationship
mutate and useSWRMutation solve related but different problems. mutate is cache-first: it addresses existing SWR keys, can write local data, can revalidate, and is available from hooks or configuration. useSWRMutation is trigger-first: it returns a trigger function that runs a remote mutation fetcher only when called. The remote mutation tests show that trigger() returns the data produced by the fetcher, updates the hook's data, and supports onSuccess and onError callbacks. That API is a better fit for actions like form submissions where the request itself is the central operation.
Sources: test/use-swr-remote-mutation.test.tsx
The remote mutation tests also show the trigger argument signature. With a key shaped like an array, the fetcher receives the key as its first argument and an object containing the trigger argument as { arg: value } in the second position. That differs from bound mutate, where the callback receives current cache data. Use this distinction when designing domain hooks: use useSWR plus bound mutate for cache updates around data already being read, and use useSWRMutation when the UI needs an explicit command that sends an argument to a mutation endpoint and then handles success or failure callbacks.
Sources: test/use-swr-remote-mutation.test.tsx
TypeScript Guidance and Next Steps
For TypeScript users, the safest default is to type the data at the useSWR<Data> boundary and let the bound mutator inherit that type. This catches accidental cache writes that do not match the hook's data shape, as shown by the tests that reject a number-returning updater for a string hook. For scoped mutations, add an explicit generic when a predicate matches a known family of keys. Keep updater callbacks defensive about undefined, because the type tests intentionally expose the previous value as optional. That matches SWR's runtime model, where a key can be mutated before any successful fetch has populated the cache.
Sources: test/type/mutate.ts
Next, read the useSWRMutation reference if your action starts with a remote command rather than a cache update, and read the SWRConfig reference if you need to understand provider scopes for global mutation. For application patterns, the optimistic UI examples show how immediate cache writes can produce a responsive interface before API revalidation finishes. For lower-level behavior, follow the local mutation tests: they are the clearest executable specification for how mutate interacts with revalidation, deduplication, local state sharing, and typed updater callbacks in this repository.
Sources: test/use-swr-local-mutation.test.tsx, test/use-swr-remote-mutation.test.tsx, test/type/mutate.ts