← Back to React Course | Chapter 10: Data Fetching & Async | Lesson 6 of 10

Mutations with React Query

A mutation in React Query is like sending a change request to the librarian, who then updates their records so everyone sees the latest version.

What Mutations Are For

While useQuery handles reading data, useMutation handles writing it — creating, updating, or deleting something on the server. It's built around a mutationFn (the actual API call) and a mutate function you call with the data to send.

Note: Think of useQuery as GET requests and useMutation as POST/PUT/DELETE requests.

Warning: Mutations don't run automatically on render like queries can — you always trigger them explicitly, usually from an event handler.

Example: What Mutations Are For

markup
// Run in your local React project (npm install required)
import { useMutation } from '@tanstack/react-query';

function AddUserForm() {
  const mutation = useMutation({
    mutationFn: newUser => fetch('/api/users', { method: 'POST', body: JSON.stringify(newUser) }),
  });
  return <button onClick={() => mutation.mutate({ name: 'Asha' })}>Add User</button>;
}

⚠️ This example uses an npm package with no CDN build available here — run this in your local React project.

Refreshing Related Data After a Mutation

After successfully adding, updating, or deleting something, the cached query for the related list (like [users]) is now stale. Calling queryClient.invalidateQueries in the mutation's onSuccess callback tells React Query to refetch that query, keeping the UI in sync with the server.

Note: Invalidate the specific query key related to what changed, rather than invalidating everything, to avoid unnecessary refetches.

Warning: Skipping invalidation after a mutation means the UI keeps showing the old list, even though the server's data has already changed.

Example: Refreshing Related Data After a Mutation

markup
// Run in your local React project (npm install required)
import { useMutation, useQueryClient } from '@tanstack/react-query';

function AddUserForm() {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: newUser => fetch('/api/users', { method: 'POST', body: JSON.stringify(newUser) }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
  });
  return <button onClick={() => mutation.mutate({ name: 'Asha' })}>Add User</button>;
}

⚠️ This example uses an npm package with no CDN build available here — run this in your local React project.

Showing Mutation Status in the UI

The object returned by useMutation includes isPending, isError, and isSuccess flags, letting the UI disable a button while the request is in flight, show an error message if it failed, or a confirmation if it succeeded — the same shape of feedback loading/error states need elsewhere.

Note: Disable the trigger button while isPending is true, to prevent duplicate submissions.

Warning: Ignoring isError means a failed mutation fails silently, with the user having no idea their action didn't actually go through.

Example: Showing Mutation Status in the UI

markup
// Run in your local React project (npm install required)
import { useMutation } from '@tanstack/react-query';

function AddUserForm() {
  const mutation = useMutation({
    mutationFn: newUser => fetch('/api/users', { method: 'POST', body: JSON.stringify(newUser) }),
  });
  return (
    <div>
      <button onClick={() => mutation.mutate({ name: 'Asha' })} disabled={mutation.isPending}>
        {mutation.isPending ? 'Adding...' : 'Add User'}
      </button>
      {mutation.isError && <p>Failed to add user</p>}
    </div>
  );
}

⚠️ This example uses an npm package with no CDN build available here — run this in your local React project.

Common Mistakes
  1. Forgetting to call invalidateQueries after a successful mutation, leaving stale cached data displayed.
  2. Not handling the mutation's isPending/isError states, giving no feedback during a slow or failed write.
  3. Calling the mutate function directly with the wrong argument shape, not matching what mutationFn expects.
Chapter Summary
  • useMutation manages create/update/delete operations, as opposed to useQuery's read operations.
  • Calling mutate(variables) triggers the mutationFn with those variables.
  • invalidateQueries tells React Query a related query's cached data is now stale and should be refetched.
  • The mutation object exposes isPending, isError, and isSuccess for showing appropriate UI feedback.
Browser Support

Requires npm install @tanstack/react-query — not available via CDN in this sandbox.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.