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.
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
exportconstPostDetail=()=>{
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.
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. Before getting started, let's cover some new terms used when defining an endpoint in a service:
For RTK Query, entities are just a name that you can give to a specific collection of data to control caching and invalidation behavior, and are defined in an entityTypes argument. For example, in an application that has both Posts and Users, you would define entityTypes: ['Posts', 'Users'] when calling createApi.
A query can provide entities to the cache. The provides argument can either be an array of string (such as ['Posts']), {type: string, id?: string|number} or a callback that returns such an array. That function will be passed the result as the first argument, the response error as the second argument, and the argument originally passed into the query method as the third argument. Note that either the result or error arguments may be undefined based on whether the query was successful or not.
A mutation can invalidate specific entities in the cache. The invalidates argument can either be an array of string (such as ['Posts']), {type: string, id?: string|number} or a callback that returns such an array. That function will be passed the result as the first argument, the response error as the second argument, and the argument originally passed into the query method as the third argument. Note that either the result or error arguments may be undefined based on whether the mutation was successful or not.
<PostDetail id={1}/>// Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)`
<PostDetail id={2}/>
<PostDetail id={3}/>
</div>
);
}
What to expect
When addPost is triggered, it would cause each PostDetail component to go back into a isFetching state because addPost invalidates the root entity, which causes every query that provides 'Posts' to be re-run. In most cases, this may not be what you want to do. Imagine if you had 100 posts on the screen that all subscribed to a getPost query โ in this case, you'd create 100 requests and send a ton of unnecessary traffic to your server, which we're trying to avoid in the first place! Even though the user would still see the last good cached result and potentially not notice anything other than their browser hiccuping, you still want to avoid this.
LIST is an arbitrary string - technically speaking, you could use anything you want here, such as ALL or *. The important thing when choosing a custom id is to make sure there is no possibility of it colliding with an id that is returned by a query result. If you have unknown ids in your query results and don't want to risk it, you can go with point 3 below.
You can add many entity types for even more control
If the concept of using an id like 'LIST' seems strange to you, you can always add another entityType and invalidate it's root, but we recommend using the id approach as shown.
App.tsx
functionApp(){
const{ data: posts }=useGetPostsQuery();
const[addPost]=useAddPostMutation();
return(
<div>
<AddPost onAdd={addPost}/>
<PostsList/>
<PostDetail id={1}/>// Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)`
<PostDetail id={2}/>
<PostDetail id={3}/>
</div>
);
}
What to expect
When addPost is fired, it will only cause the PostsList to go into an isFetching state because addPost only invalidates the 'LIST' id, which causes getPosts to rerun (because it provides that specific id). So in your network tab, you would only see 1 new request fire for GET /posts. Once that resolves and assuming it returned updated data for ids 1, 2, and 3, the PostDetail components would then rerender with the latest data.
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.