Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects. For this purpose, TanStack Query exports a injectMutation function.
Here's an example of a mutation that adds a new todo to the server:
@Component({
template: `
<div>
@if (mutation.isPending()) {
<span>Adding todo...</span>
} @else if (mutation.isError()) {
<div>An error occurred: {{ mutation.error()?.message }}</div>
} @else if (mutation.isSuccess()) {
<div>Todo added!</div>
}
<button (click)="mutation.mutate(1)">Create Todo</button>
</div>
`,
})
export class TodosComponent {
todoService = inject(TodoService)
mutation = injectMutation(() => ({
mutationFn: (todoId: number) =>
lastValueFrom(this.todoService.create(todoId)),
}))
}
@Component({
template: `
<div>
@if (mutation.isPending()) {
<span>Adding todo...</span>
} @else if (mutation.isError()) {
<div>An error occurred: {{ mutation.error()?.message }}</div>
} @else if (mutation.isSuccess()) {
<div>Todo added!</div>
}
<button (click)="mutation.mutate(1)">Create Todo</button>
</div>
`,
})
export class TodosComponent {
todoService = inject(TodoService)
mutation = injectMutation(() => ({
mutationFn: (todoId: number) =>
lastValueFrom(this.todoService.create(todoId)),
}))
}
A mutation can only be in one of the following states at any given moment:
Beyond those primary states, more information is available depending on the state of the mutation:
In the example above, you also saw that you can pass variables to your mutations function by calling the mutate function with a single variable or object.
Even with just variables, mutations aren't all that special, but when used with the onSuccess option, the Query Client's invalidateQueries method and the Query Client's setQueryData method, mutations become a very powerful tool.
It's sometimes the case that you need to clear the error or data of a mutation request. To do this, you can use the reset function to handle this:
@Component({
standalone: true,
selector: 'todo-item',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="todoForm" (ngSubmit)="onCreateTodo()">
@if (mutation.error()) {
<h5 (click)="mutation.reset()">{{ mutation.error() }}</h5>
}
<input type="text" formControlName="title" />
<br />
<button type="submit">Create Todo</button>
</form>
`,
})
export class TodosComponent {
mutation = injectMutation(() => ({
mutationFn: createTodo,
}))
fb = inject(NonNullableFormBuilder)
todoForm = this.fb.group({
title: this.fb.control('', {
validators: [Validators.required],
}),
})
title = toSignal(this.todoForm.controls.title.valueChanges, {
initialValue: '',
})
onCreateTodo = () => {
this.mutation.mutate(this.title())
}
}
@Component({
standalone: true,
selector: 'todo-item',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="todoForm" (ngSubmit)="onCreateTodo()">
@if (mutation.error()) {
<h5 (click)="mutation.reset()">{{ mutation.error() }}</h5>
}
<input type="text" formControlName="title" />
<br />
<button type="submit">Create Todo</button>
</form>
`,
})
export class TodosComponent {
mutation = injectMutation(() => ({
mutationFn: createTodo,
}))
fb = inject(NonNullableFormBuilder)
todoForm = this.fb.group({
title: this.fb.control('', {
validators: [Validators.required],
}),
})
title = toSignal(this.todoForm.controls.title.valueChanges, {
initialValue: '',
})
onCreateTodo = () => {
this.mutation.mutate(this.title())
}
}
injectMutation comes with some helper options that allow quick and easy side-effects at any stage during the mutation lifecycle. These come in handy for both invalidating and refetching queries after mutations and even optimistic updates
mutation = injectMutation(() => ({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
}))
mutation = injectMutation(() => ({
mutationFn: addTodo,
onMutate: (variables) => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
}))
When returning a promise in any of the callback functions it will first be awaited before the next callback is called:
mutation = injectMutation(() => ({
mutationFn: addTodo,
onSuccess: async () => {
console.log("I'm first!")
},
onSettled: async () => {
console.log("I'm second!")
},
}))
mutation = injectMutation(() => ({
mutationFn: addTodo,
onSuccess: async () => {
console.log("I'm first!")
},
onSettled: async () => {
console.log("I'm second!")
},
}))
You might find that you want to trigger additional callbacks beyond the ones defined on injectMutation when calling mutate. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the `