Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/SaulMoro/ngrx-rtk-query/llms.txt

Use this file to discover all available pages before exploring further.

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?);
options
UseMutationOptions
Optional configuration object

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
isLoading
Signal<boolean>
true while the mutation is in progress
isSuccess
Signal<boolean>
true if the mutation succeeded
isError
Signal<boolean>
true if the mutation failed
error
Signal<Error | undefined>
The error object if the mutation failed
reset
() => void
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