How to Use the React Query useMutation Hook in React Components
The useMutation hook from @tanstack/react-query handles asynchronous server-side mutations with built-in loading states, error handling, optimistic updates, and automatic cache invalidation.
While the facebook/react repository provides the foundational hooks like useState and useEffect found in packages/react/src/ReactHooks.js, data mutations require external libraries. The useMutation hook from TanStack Query (formerly React Query) integrates seamlessly with React's architecture to manage create, update, and delete operations while respecting the core hook rules defined in the React source.
What is the React Query useMutation Hook?
The useMutation hook is not part of the core facebook/react library. Instead, it ships with @tanstack/react-query, a dedicated data-fetching library. According to the TanStack Query source code in packages/react-query/src/useMutation.ts, this hook manages the lifecycle of asynchronous mutation functions—typically API calls that modify server state.
The hook returns an object containing:
mutateandmutateAsync: Functions to trigger the mutationstatus,isLoading,isError,isSuccess: Boolean flags for UI statedata: The resolved value from the mutation functionerror: Any error thrown during execution
Setting Up useMutation in Your Component
Basic Mutation with Optimistic Updates
The most common pattern involves defining a mutation function and configuring lifecycle callbacks. In packages/react/src/ReactHooks.js, React establishes the hook contract that useMutation must follow—specifically, hooks must be called at the top level of a functional component.
Here is a complete example implementing a todo creation mutation with optimistic updates:
import {useMutation, useQueryClient} from '@tanstack/react-query';
import axios from 'axios';
type Todo = {id: number; title: string};
export default function AddTodo() {
const queryClient = useQueryClient();
// 1️⃣ Define the mutation function
const addTodo = async (title: string) => {
const {data} = await axios.post<Todo>('/api/todos', {title});
return data;
};
// 2️⃣ Create the mutation hook
const {
mutate,
isLoading,
isError,
error,
isSuccess,
data: newTodo,
} = useMutation(addTodo, {
// 3️⃣ Optimistically update the cached list of todos
onMutate: async (title) => {
await queryClient.cancelQueries(['todos']);
const previous = queryClient.getQueryData<Todo[]>(['todos']);
queryClient.setQueryData<Todo[]>(['todos'], (old) => [
...(old ?? []),
{id: Date.now(), title},
]);
return {previous};
},
// 4️⃣ Roll back on error
onError: (err, newTitle, context) => {
if (context?.previous) {
queryClient.setQueryData(['todos'], context.previous);
}
},
// 5️⃣ Refetch the list after success
onSuccess: () => {
queryClient.invalidateQueries(['todos']);
},
});
// Simple UI
const [title, setTitle] = React.useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate(title);
setTitle('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="New todo"
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Saving…' : 'Add'}
</button>
{isError && <p style={{color: 'red'}}>Error: {(error as Error).message}</p>}
{isSuccess && newTodo && (
<p style={{color: 'green'}}>Added: {newTodo.title}</p>
)}
</form>
);
}
Using mutateAsync for Async/Await Patterns
When you need to execute mutations sequentially or handle promises explicitly, use mutateAsync instead of mutate. This variant returns a Promise that resolves with the mutation result or rejects with an error.
import {useMutation} from '@tanstack/react-query';
import fetch from 'node-fetch';
function useDeleteUser() {
return useMutation(
(userId: number) => fetch(`/api/users/${userId}`, {method: 'DELETE'}).then(r => r.json())
);
}
export default function DeleteButton({userId}: {userId: number}) {
const {mutateAsync, isLoading, isError, error} = useDeleteUser();
const handleDelete = async () => {
try {
await mutateAsync(userId);
alert('User deleted');
} catch (e) {
console.error(e);
}
};
return (
<button onClick={handleDelete} disabled={isLoading}>
{isLoading ? 'Deleting…' : 'Delete'}
</button>
);
}
Advanced Patterns with useMutation
Encapsulating Logic in Custom Hooks
To promote reusability and separate concerns, wrap useMutation in custom hooks. This pattern keeps your components clean while centralizing cache invalidation logic.
import {useMutation, useQueryClient} from '@tanstack/react-query';
import axios from 'axios';
export function useUpdateProfile() {
const queryClient = useQueryClient();
return useMutation(
(profile) => axios.put('/api/profile', profile).then((res) => res.data),
{
onSuccess: () => queryClient.invalidateQueries(['profile']),
}
);
}
Cache Invalidation and Optimistic Updates
The useMutation hook provides four key lifecycle callbacks that interact with the query cache:
onMutate: Fires before the mutation executes. Use this to implement optimistic updates by manually setting query data viaqueryClient.setQueryData.onSuccess: Fires when the mutation resolves. Typically used to invalidate queries withqueryClient.invalidateQueriesto refetch fresh data.onError: Fires when the mutation fails. Use this to roll back optimistic updates by restoring previous cache values from the context returned byonMutate.onSettled: Fires when the mutation completes regardless of outcome. Useful for cleanup operations.
React Core Integration Architecture
While the useMutation implementation resides in the TanStack Query repository at packages/react-query/src/useMutation.ts, it fundamentally depends on the hook architecture defined in facebook/react. Specifically, the core React hooks implementation in packages/react/src/ReactHooks.js establishes the contract that external libraries must follow: hooks must be called at the top level of functional components, cannot be called conditionally, and must preserve state across renders.
When you install @tanstack/react-query, it consumes the standard React hook APIs to manage its internal state, ensuring seamless integration with the React component lifecycle defined in the core repository.
Summary
- The
useMutationhook is provided by@tanstack/react-query, not the corefacebook/reactlibrary. - It manages asynchronous mutation lifecycles through the
mutate(fire-and-forget) andmutateAsync(promise-returning) functions. - Lifecycle callbacks (
onMutate,onSuccess,onError,onSettled) enable optimistic updates and automatic cache invalidation viauseQueryClient. - The hook follows the same rules as built-in React hooks defined in
packages/react/src/ReactHooks.js, ensuring compatibility with the React component model.
Frequently Asked Questions
Is useMutation included in the core React library?
No. The useMutation hook is part of TanStack Query (@tanstack/react-query), a separate data-fetching library. The core facebook/react repository provides foundational hooks like useState and useEffect in packages/react/src/ReactHooks.js, but mutations require external state management solutions that build upon these primitives.
What is the difference between mutate and mutateAsync?
The mutate function triggers the mutation immediately and returns void, making it ideal for event handlers where you rely on the hook's returned state flags (isLoading, isError) for UI updates. The mutateAsync function returns a Promise that resolves with the mutation result or rejects with an error, enabling async/await patterns for sequential operations or explicit try-catch error handling.
How do I update the cache after a successful mutation?
Use the onSuccess callback in your useMutation configuration to access the queryClient instance obtained from useQueryClient(). Call queryClient.invalidateQueries(['yourQueryKey']) to mark related queries as stale and trigger automatic refetching. For immediate UI updates without waiting for the server, use onMutate to optimistically update the cache with queryClient.setQueryData, then roll back in onError if the request fails.
Can I use useMutation with React Class components?
No. The useMutation hook follows React's Hooks API, which is only available in functional components. If you need mutation capabilities in a class component, you must either convert the component to a functional component or use the render-prop based Mutation component from legacy versions of React Query (deprecated in favor of hooks).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →