Mutations
Mutations allow us to trigger and track the status of an async operation meant to have side effects. While queries are meant to read data, mutations are meant to write data. In terms of REST, queries usually handle GET
requests and mutations handle POST
, PUT
, PATCH
, and DELETE
requests (without limiting you to do so).
Similarly to queries, mutations are defined with the useMutation()
composable.
Basic usage
A basic mutation is usually defined with a single property, mutation
, which is a function that performs the async operation.
<script setup lang="ts">
import { ref } from 'vue'
import { useMutation } from '@pinia/colada'
const {
mutate: createTodo,
status,
asyncStatus,
} = useMutation({
mutation: (todoText: string) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text: todoText }),
}),
})
const todoText = ref('')
</script>
<template>
<form @submit.prevent="createTodo(todoText)">
<input v-model="todoText">
<button :disabled="asyncStatus === 'loading'">
Add todo
</button>
</form>
</template>
In the example above, it's worth noting that the mutation
function has one parameter, todoText
, which is passed to the createTodo
function when the form is submitted. We could also directly use the todoText.value
directly in the mutation
function. The main advantage of defining a parameter is that it makes it available in mutation hooks, like onSuccess
or onError
. In general, it's a good practice to define the parameters of the mutation function. They will also get reflected on the variables
property of the mutation.
<script setup lang="ts">
import { useMutation } from '@pinia/colada'
const {
variables,
} = useMutation({
mutation: (todoText: string) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text: todoText }),
}),
})
</script>
Mutations ❤️ Queries
Using mutations allows us to easily keep the changed data up to date. We have multiple strategies to do so:
- Invalidate queries that depend on the data that was changed.
- Optimistic updates to update the UI before the mutation is completed.
Reusable mutations
Similarly to queries, mutations can be defined and reused across components with the defineMutation()
function.
import { ref } from 'vue'
import { defineMutation, useMutation } from '@pinia/colada'
export const useCreateTodo = defineMutation(() => {
const todoText = ref('')
const { mutate: createTodo, ...mutation } = useMutation({
mutation: () =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text: todoText.value }),
}),
})
return {
...mutation,
createTodo,
// expose the todoText ref
todoText,
}
})
<script setup lang="ts">
import { useCreateTodo } from './mutations/todos'
const { createTodo, status, asyncStatus, todoText } = useCreateTodo()
</script>
<template>
<form @submit.prevent="() => {}">
<input v-model="todoText">
<button :disabled="asyncStatus === 'loading'">
Add todo
</button>
</form>
</template>
When you just want to organize your mutations, you can also pass an object of options to defineMutation()
:
// src/mutations/todos.ts
import { defineMutation } from '@pinia/colada'
export const useCreateTodo = defineMutation({
mutation: (todoText: string) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text: todoText }),
}),
})
Executing mutations
Mutations expose two different methods for executing the mutation:
mutate
: The main method to execute the mutation. It accepts the same parameters as the mutation function and does not return anything.mutateAsync
: A method that returns a promise that resolves when the mutation is completed. It accepts the same parameters as the mutation function.
<script setup lang="ts">
import { useMutation } from '@pinia/colada'
const {
// hover over to inspect the types
mutate,
mutateAsync,
} = useMutation({
mutation: (todoText: string) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text: todoText }),
}),
})
</script>
Most of the time, using mutate
should be easier to use as it catches any errors thrown by the mutation. On the other hand mutateAsync
is useful when you need to handle the errors yourself or to wait for the mutation to complete.
Mutation state
The mutation returns similar properties to queries, like state
, data
, error
, status
, asyncStatus
, etc. However, mutations are, by default, not global.