Mutation
SWRV provides the mutate and useSWRVMutation APIs for mutating remote data and the related cache.
TIP
Composable snippets on this page assume they are called inside setup() or <script setup>.
mutate
There are 2 ways to use mutate: the global mutate API which can mutate any key, and the bound mutate API which only mutates the data for the current key.
Global mutate
The recommended way to get the scoped or global mutator is to use useSWRVConfig():
import { useSWRVConfig } from "swrv";
const { mutate } = useSWRVConfig();
await mutate(key, data, options);You can also import it globally:
import { mutate } from "swrv";
await mutate(key, data, options);WARNING
Using global mutate(key) with only the key parameter will not update the cache unless there is a mounted component using useSWRV with the same key. It is a revalidation signal, not a write by itself.
Bound mutate
Bound mutate is the short path for mutating the current key with data. It behaves like the global mutate function, but the key is already bound to the useSWRV call.
<script setup lang="ts">
import useSWRV from "swrv";
const { data, mutate } = useSWRV("/api/user", fetcher);
async function uppercaseName() {
if (!data.value) {
return;
}
const newName = data.value.name.toUpperCase();
await requestUpdateUsername(newName);
await mutate({ ...data.value, name: newName });
}
</script>Revalidation
When you call mutate(key) or bound mutate() without any data, it triggers a revalidation for the resource:
<script setup lang="ts">
import { useSWRVConfig } from "swrv";
const { mutate } = useSWRVConfig();
function logout() {
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
mutate("/api/user");
}
</script>This is useful after auth changes, remote invalidations, or any action where the cache should be refetched rather than directly replaced.
It broadcasts to consumers under the same cache provider scope. If you are using a custom provider boundary, prefer the scoped mutate from useSWRVConfig() so you mutate the cache that the current components are actually using.
API
await mutate(key, data?, options?);Parameters
key: same asuseSWRV'skey, but a function behaves as a filter functiondata: data to update the client cache, or an async function for the remote mutationoptions: accepts the following options:optimisticData: data to write immediately, or a function that derives optimistic data from the current cached valuerevalidate = true: whether SWRV should revalidate after the mutation resolvespopulateCache = true: whether the resolved mutation result should be written to the cache, or a function that merges the mutation result with the current cached valuerollbackOnError = true: whether optimistic data should be rolled back when the remote mutation failsthrowOnError = true: whether the mutate call should rethrow the error
Return values
mutate returns the resolved value of the data parameter. If an error is thrown while executing the mutation, the error is rethrown unless throwOnError is disabled.
try {
const user = await mutate("/api/user", updateUser(newUser));
} catch (error) {
// handle an error while updating the user here
}useSWRVMutation
SWRV also provides useSWRVMutation as a composable for remote mutations. Remote mutations are triggered manually instead of automatically like useSWRV.
This composable does not share local state with other useSWRVMutation calls, but it does share the same cache store as useSWRV.
API
const { data, error, isMutating, reset, trigger } = useSWRVMutation(key, fetcher, options);Parameters
key: same asmutate'skeyfetcher(key, { arg }): an async function for remote mutationoptions: an optional object with properties such as:optimisticData: same asmutate'soptimisticDatarevalidate = true: same asmutate'srevalidatepopulateCache = false: same asmutate'spopulateCache, but defaulting tofalserollbackOnError = true: same asmutate'srollbackOnErrorthrowOnError = true: same asmutate'sthrowOnErroronSuccess(data, key, config)onError(error, key, config)
Return values
data: data for the given key returned from the fetchererror: error thrown by the fetchertrigger(arg, options): a function to trigger a remote mutationreset: resetsdata,error, andisMutatingisMutating: whether there is an ongoing remote mutation
trigger also accepts per-call options so you can override optimistic updates or rollback behavior for one mutation without changing the base composable configuration.
It returns the remote mutation result when the request succeeds.
Basic usage
<script setup lang="ts">
import useSWRVMutation from "swrv/mutation";
const { trigger, isMutating, error } = useSWRVMutation(
"/api/user",
async (key, { arg }: { arg: { name: string } }) => {
const response = await fetch(key, {
body: JSON.stringify(arg),
headers: { "Content-Type": "application/json" },
method: "PATCH",
});
return response.json() as Promise<{ id: string; name: string }>;
},
);
async function submit(name: string) {
try {
const updatedUser = await trigger({ name });
console.log(updatedUser);
} catch {
// handle the error here
}
}
</script>You can also override options at trigger time:
await trigger(
{ name: "Ada" },
{
optimisticData: (current) => (current ? { ...current, name: "Ada" } : current),
rollbackOnError: true,
},
);Defer loading data until needed
You can also use useSWRVMutation for loading data. It does not start requesting until trigger is called:
<script setup lang="ts">
import { ref } from "vue";
import useSWRVMutation from "swrv/mutation";
const show = ref(false);
const { data: user, trigger } = useSWRVMutation("/api/user", (url) =>
fetch(url).then((response) => response.json()),
);
async function showUser() {
await trigger();
show.value = true;
}
</script>That makes it useful for modal-driven or user-triggered flows where loading should not start at mount time.
Optimistic updates
In many cases, applying local mutations to data is a good way to make changes feel faster. With optimisticData, you can update your local data manually while waiting for the remote mutation to finish. Combined with rollbackOnError, you can also control when to roll that data back.
<script setup lang="ts">
import { useSWRVConfig } from "swrv";
import useSWRV from "swrv";
const { mutate } = useSWRVConfig();
const { data } = useSWRV("/api/user", fetcher);
async function uppercaseName() {
if (!data.value) {
return;
}
const newName = data.value.name.toUpperCase();
const user = { ...data.value, name: newName };
await mutate("/api/user", updateUser(user), {
optimisticData: user,
rollbackOnError(error) {
return error instanceof Error ? error.name !== "AbortError" : true;
},
});
}
</script>The remote updater should be a promise or async function that returns the updated data.
You can also pass a function to optimisticData so it derives the optimistic value from the current cache:
await mutate("/api/user", updateUserName(newName), {
optimisticData: (user) => (user ? { ...user, name: newName } : user),
rollbackOnError: true,
});The same pattern works with useSWRVMutation and trigger:
const { trigger } = useSWRVMutation("/api/user", updateUserName);
await trigger(newName, {
optimisticData: (user) => (user ? { ...user, name: newName } : user),
rollbackOnError: true,
});Rollback on errors
When optimisticData is set, it is possible for the optimistic value to appear first and the remote mutation to fail later. In that case, rollbackOnError restores the previous cache value so the UI returns to a correct state:
await mutate("/api/user", failingRequest(), {
optimisticData: { id: "1", name: "Grace" },
rollbackOnError: true,
});Update cache after mutation
Use populateCache to decide what should be written back:
await mutate("/api/user", patchUser(), {
populateCache: (result, current) => ({ ...current, ...result }),
revalidate: false,
});Set populateCache: false when the mutation response should not replace the cache.
The same pattern works with useSWRVMutation:
useSWRVMutation("/api/todos", updateTodo, {
populateCache: (updatedTodo, todos) => {
const filteredTodos = todos.filter((todo) => todo.id !== "1");
return [...filteredTodos, updatedTodo];
},
revalidate: false,
});When combined with optimisticData and rollbackOnError, populateCache gives you the full optimistic UI flow without an extra fetch.
Avoid race conditions
Both mutate and useSWRVMutation coordinate with useSWRV so late request responses do not overwrite newer mutation results. This is especially important when a mutation and a background revalidation overlap.
Mutate based on current data
Mutation callbacks receive the current cached value:
await mutate(
"/api/todos",
async (todos) => {
const updatedTodo = await patchTodo("1", { done: true });
const filteredTodos = todos?.filter((todo) => todo.id !== "1") ?? [];
return [...filteredTodos, updatedTodo];
},
{ revalidate: false },
);Mutate multiple items
Use a filter function to target multiple keys:
import { mutate } from "swrv";
await mutate((key) => typeof key === "string" && key.startsWith("/api/todos?"), undefined, {
revalidate: true,
});This also works with other key shapes:
await mutate((key) => Array.isArray(key) && key[0] === "item", undefined, {
revalidate: false,
});Use this sparingly. The filter runs against every existing cache key, so the safest filters guard the key shape before reading from it.
You can also use the filter form to clear all cached data, which is useful on logout:
const clearCache = () =>
mutate(() => true, undefined, {
revalidate: false,
});