# How to Use the React Query useMutation Hook in React Components

> Master React Query's useMutation hook for data mutations. Simplify async operations, loading states, errors, and cache updates in your React components and boost app performance.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: tutorial
- Published: 2026-02-14

---

**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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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:
- **`mutate`** and **`mutateAsync`**: Functions to trigger the mutation
- **`status`**, **`isLoading`**, **`isError`**, **`isSuccess`**: Boolean flags for UI state
- **`data`**: The resolved value from the mutation function
- **`error`**: 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`](https://github.com/facebook/react/blob/main/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:

```tsx
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.

```tsx
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.

```tsx
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 via `queryClient.setQueryData`.
- **`onSuccess`**: Fires when the mutation resolves. Typically used to **invalidate queries** with `queryClient.invalidateQueries` to 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 by `onMutate`.
- **`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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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 `useMutation` hook is provided by `@tanstack/react-query`, not the core `facebook/react` library.
- It manages asynchronous mutation lifecycles through the `mutate` (fire-and-forget) and `mutateAsync` (promise-returning) functions.
- Lifecycle callbacks (`onMutate`, `onSuccess`, `onError`, `onSettled`) enable optimistic updates and automatic cache invalidation via `useQueryClient`.
- The hook follows the same rules as built-in React hooks defined in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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).