Mutations#

Unlike useQuery, useMutation returns a tuple. The first item in the tuple is the trigger function and the second element contains an object with status, error, and data.

Unlike the useQuery hook, the useMutation hook doesn't execute automatically. To run a mutation you have to call the trigger function returned as the first tuple value from the hook.

Example of all mutation endpoint options
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { Post } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
// note: an optional `queryFn` may be used in place of `query`
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
// Pick out data and prevent nested properties in a hook or selector
transformResponse: (response: { data: Post }) => response.data,
invalidatesTags: ['Post'],
// onQuery is useful for optimistic updates
// The 2nd parameter is the destructured `MutationLifecycleApi`
async onQuery(
arg,
{ dispatch, getState, resultPromise, requestId, extra, getCacheEntry }
) {},
// The 2nd parameter is the destructured `MutationCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cleanup,
firstValueResolved,
getCacheEntry,
}
) {},
}),
}),
})
info

Notice the onQuery method? Be sure to check out how it can be used for optimistic updates

Type interfaces#

Mutation endpoint definition
export type MutationDefinition<
QueryArg,
BaseQuery extends BaseQueryFn,
TagTypes extends string,
ResultType,
ReducerPath extends string = string,
Context = Record<string, any>
> = BaseEndpointDefinition<QueryArg, BaseQuery, ResultType> & {
type: DefinitionType.mutation
invalidatesTags?: ResultDescription<TagTypes, ResultType, QueryArg>
providesTags?: never
onQuery?(
arg: QueryArg,
{
dispatch,
getState,
resultPromise,
requestId,
extra,
getCacheEntry,
}: MutationLifecycleApi
): Promise<void>
onCacheEntryAdded?(
arg: QueryArg,
{
dispatch,
getState,
extra,
requestId,
cleanup,
firstValueResolved,
getCacheEntry,
}: MutationCacheLifecycleApi
): Promise<void>
}

Basic Mutation#

This is a modified version of the complete example you can see at the bottom of the page to highlight the updatePost mutation. In this scenario, a post is fetched with useQuery, and then a EditablePostName component is rendered that allows us to edit the name of the post.

src/features/posts/PostDetail.tsx
export const PostDetail = () => {
const { id } = useParams<{ id: any }>()
const { data: post } = useGetPostQuery(id)
const [
updatePost, // This is the mutation trigger
{ isLoading: isUpdating }, // You can use the `isLoading` flag, or do custom logic with `status`
] = useUpdatePostMutation()
return (
<Box p={4}>
<EditablePostName
name={post.name}
onUpdate={(name) => {
// If you want to immediately access the result of a mutation, you need to chain `.unwrap()`
// if you actually want the payload or to catch the error.
// Example: `updatePost().unwrap().then(fulfilled => console.log(fulfilled)).catch(rejected => console.error(rejected))
return (
// Execute the trigger with the `id` and updated `name`
updatePost({ id, name })
)
}}
isLoading={isUpdating}
/>
</Box>
)
}

Advanced mutations with revalidation#

In the real world, it's very common that a developer would want to resync their local data cache with the server after performing a mutation (aka "revalidation"). RTK Query takes a more centralized approach to this and requires you to configure the invalidation behavior in your API service definition. See Advanced Invalidation with abstract tag IDs for details on advanced invalidation handling with RTK Query.

Example - Commented Posts Service#

This is an example of a CRUD service for Posts. This implements the Selectively invalidating lists strategy and will most likely serve as a good foundation for real applications.

src/app/services/posts.ts
// Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export interface Post {
id: number
name: string
}
type PostsResponse = Post[]
export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
// Provides a list of `Posts` by `id`.
// If any mutation is executed that `invalidate`s any of these tags, this query will re-run to be always up-to-date.
// The `LIST` id is a "virtual id" we just made up to be able to invalidate this query specifically if a new `Posts` element was added.
providesTags: (result) =>
// is result available?
result
? // successful query
[
...result.map(({ id }) => ({ type: 'Posts', id } as const)),
{ type: 'Posts', id: 'LIST' },
]
: // an error occurred, but we still want to refetch this query when `{ type: 'Posts', id: 'LIST' }` is invalidated
[{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `posts`,
method: 'POST',
body,
}
},
// Invalidates all Post-type queries providing the `LIST` id - after all, depending of the sort order,
// that newly created post could show up in any lists.
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `posts/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
updatePost: build.mutation<Post, Partial<Post>>({
query(data) {
const { id, ...body } = data
return {
url: `posts/${id}`,
method: 'PUT',
body,
}
},
// Invalidates all queries that subscribe to this Post `id` only.
// In this case, `getPost` will be re-run. `getPosts` *might* rerun, if this id was under it's results.
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
deletePost: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `posts/${id}`,
method: 'DELETE',
}
},
// Invalidates all queries that subscribe to this Post `id` only.
invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const {
useGetPostsQuery,
useAddPostMutation,
useGetPostQuery,
useUpdatePostMutation,
useDeletePostMutation,
} = postApi