Script Valley
React.js: Complete Course
State Management with Zustand and React QueryLesson 6.4

React Query mutations: creating, updating, and deleting data

useMutation hook, mutate function, onSuccess callback, onError callback, optimistic updates, invalidateQueries, mutation state, mutateAsync

Mutations with useMutation

useMutation handles data writes: POST, PUT, DELETE requests. After a successful mutation, invalidate related queries to refetch fresh data automatically.

Basic Mutation

import { useMutation, useQueryClient } from '@tanstack/react-query';

function AddTodo() {
  const queryClient = useQueryClient();

  const { mutate, isPending, isError } = useMutation({
    mutationFn: (newTodo) =>
      fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
        headers: { 'Content-Type': 'application/json' },
      }).then(r => r.json()),

    onSuccess: () => {
      // Invalidate the todos query so the list refetches
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },

    onError: (error) => {
      console.error('Failed to add todo:', error);
    },
  });

  function handleSubmit(e) {
    e.preventDefault();
    mutate({ text: 'New todo', completed: false });
  }

  return (
    <form onSubmit={handleSubmit}>
      <button disabled={isPending}>
        {isPending ? 'Adding...' : 'Add Todo'}
      </button>
      {isError && <p>Failed to add.</p>}
    </form>
  );
}

Use mutateAsync when you need to await the mutation result in a try/catch. Use mutate when you handle callbacks via onSuccess/onError. Always invalidate related query keys after successful writes so the UI reflects the latest server state.

Up next

Combining Zustand and React Query in a real app

Sign in to track progress