5 min read
0%

Optimistic UI Updates

Back to Blog
Optimistic UI Updates

Optimistic UI Updates

Optimistic updates apply changes to the UI immediately, before the server confirms them. On failure, you roll back. This makes mutations feel instant.

The Pattern

function TodoList() {
  const [todos, setTodos] = useState(initialTodos);

  async function addTodo(text) {
    const optimisticTodo = { id: crypto.randomUUID(), text, status: 'pending' };

    // Update UI immediately
    setTodos(prev => [...prev, optimisticTodo]);

    try {
      const savedTodo = await api.createTodo(text);
      // Replace the optimistic entry with the server response
      setTodos(prev => prev.map(t =>
        t.id === optimisticTodo.id ? savedTodo : t
      ));
    } catch (err) {
      // Roll back on failure
      setTodos(prev => prev.filter(t => t.id !== optimisticTodo.id));
      showErrorToast('Failed to add todo');
    }
  }
}

React 19: useOptimistic

React 19 provides a hook designed for this pattern:

const [optimisticTodos, addOptimistic] = useOptimistic(
  todos,
  (state, newTodo) => [...state, newTodo]
);

async function handleAdd(text) {
  addOptimistic({ id: 'temp', text, pending: true });
  await api.createTodo(text); // revalidation happens after this
}

// Render optimisticTodos — reflects pending state automatically

useOptimistic reverts the optimistic state when the server response arrives.

TanStack Query Integration

const mutation = useMutation({
  mutationFn: (text) => api.createTodo(text),
  onMutate: async (text) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const previous = queryClient.getQueryData(['todos']);

    queryClient.setQueryData(['todos'], old => [
      ...old,
      { id: 'temp', text, pending: true },
    ]);

    return { previous };
  },
  onError: (err, text, context) => {
    queryClient.setQueryData(['todos'], context.previous);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

When Optimistic Updates Work

  • Low failure rate operations (adding a like, toggling a setting)
  • User has reliable connectivity
  • The operation is idempotent or easy to reverse visually

When They Don’t

  • Operations that frequently fail (form submissions with server validation)
  • Irreversible high-stakes actions (payments, permanent deletions — use confirmation dialogs instead)
  • When you need the server’s response to continue (e.g., a generated ID for the next step)

Canvas is not supported in your browser