useMutation is an auto-generated hook for performing data modifications (create, update, delete operations).
Overview
For an endpoint named addPost, the generated hook will be useAddPostMutation.
export const postsApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (build) => ({
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: '/posts',
method: 'POST',
body,
}),
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
}),
});
export const { useAddPostMutation } = postsApi;
Signature
const [trigger, result] = useXxxMutation(options?);
Optional configuration object
Share mutation state across multiple components using the same cache key
Return Value
Returns an object with trigger function and mutation state:
trigger
(arg: MutationArg) => Promise
Function to trigger the mutation. Returns a promise with unwrap() method
data
Signal<ResultType | undefined>
The returned data from the mutation
true while the mutation is in progress
true if the mutation succeeded
true if the mutation failed
error
Signal<Error | undefined>
The error object if the mutation failed
Function to reset the mutation state
Usage Examples
Basic Usage
import { useAddPostMutation } from './api';
@Component({
selector: 'app-add-post',
template: `
<form (submit)="onSubmit()">
<input [(ngModel)]="title" placeholder="Title" />
<button
type="submit"
[disabled]="addPost.isLoading()">
{{ addPost.isLoading() ? 'Adding...' : 'Add Post' }}
</button>
</form>
@if (addPost.isError()) {
<p class="error">{{ addPost.error() }}</p>
}
`,
})
export class AddPostComponent {
addPost = useAddPostMutation();
title = '';
onSubmit() {
this.addPost({ title: this.title });
}
}
With Promise Handling (unwrap)
export class AddPostComponent {
addPost = useAddPostMutation();
title = '';
async onSubmit() {
try {
const newPost = await this.addPost({
title: this.title
}).unwrap();
console.log('Post created:', newPost);
this.title = ''; // Clear form
} catch (error) {
console.error('Failed to add post:', error);
}
}
}
With Then/Catch
export class AddPostComponent {
addPost = useAddPostMutation();
onSubmit() {
this.addPost({ title: this.title })
.unwrap()
.then((newPost) => {
console.log('Success:', newPost);
this.router.navigate(['/posts', newPost.id]);
})
.catch((error) => {
console.error('Error:', error);
});
}
}
Update Mutation
export const postsApi = createApi({
endpoints: (build) => ({
updatePost: build.mutation<Post, { id: number; data: Partial<Post> }>({
query: ({ id, data }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: data,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
}),
});
export const { useUpdatePostMutation } = postsApi;
// Component
export class EditPostComponent {
updatePost = useUpdatePostMutation();
postId = input.required<number>();
onSave(data: Partial<Post>) {
this.updatePost({
id: this.postId(),
data
}).unwrap();
}
}
Delete Mutation
export const postsApi = createApi({
endpoints: (build) => ({
deletePost: build.mutation<void, number>({
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE',
}),
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
{ type: 'Posts', id: 'LIST' },
],
}),
}),
});
export const { useDeletePostMutation } = postsApi;
// Component
export class PostComponent {
deletePost = useDeletePostMutation();
async onDelete(postId: number) {
if (confirm('Delete this post?')) {
try {
await this.deletePost(postId).unwrap();
this.router.navigate(['/posts']);
} catch (error) {
alert('Failed to delete post');
}
}
}
}
Reset Mutation State
export class AddPostComponent {
addPost = useAddPostMutation();
onSubmit() {
this.addPost({ title: 'New Post' });
}
clearError() {
this.addPost.reset();
}
}
Fixed Cache Key (Shared State)
// Component 1
export class UploadComponent {
upload = useUploadFileMutation({ fixedCacheKey: 'file-upload' });
onUpload(file: File) {
this.upload(file);
}
}
// Component 2 - Shows same upload progress
export class UploadStatusComponent {
upload = useUploadFileMutation({ fixedCacheKey: 'file-upload' });
// Shares state with UploadComponent
progress = computed(() => {
if (this.upload.isLoading()) return 'Uploading...';
if (this.upload.isSuccess()) return 'Complete!';
return 'Ready';
});
}
Accessing Mutation State in Template
@Component({
template: `
<button
(click)="addPost({ title: 'New' })"
[disabled]="addPost.isLoading()">
Add Post
</button>
@if (addPost.isLoading()) {
<p>Creating post...</p>
}
@if (addPost.isSuccess()) {
<p>Post created: {{ addPost.data()?.title }}</p>
}
@if (addPost.isError()) {
<p>Error: {{ addPost.error() }}</p>
}
`,
})
export class AddPostComponent {
addPost = useAddPostMutation();
}
Optimistic Updates
export const postsApi = createApi({
endpoints: (build) => ({
updatePost: build.mutation<Post, { id: number; data: Partial<Post> }>({
query: ({ id, data }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: data,
}),
async onQueryStarted({ id, data }, { dispatch, queryFulfilled }) {
// Optimistically update the cache
const patchResult = dispatch(
postsApi.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, data);
})
);
try {
await queryFulfilled;
} catch {
// Revert on error
patchResult.undo();
}
},
}),
}),
});
Always use unwrap() when you need to handle success/error cases. Without it, the promise will always resolve, even on errors.
Mutations don’t automatically trigger until you call the trigger function. Unlike queries, they are always “lazy”.
See Also