
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)









