Blog>
Snippets

Implementing Optimistic Updates in TanStack Store

Present a code example that implements optimistic updates using TanStack Store, allowing the UI to update immediately before the server response is received to improve perceived performance.
import { useMutation } from 'tanstack/react-query';

// Assume we have a todos store and a function to update the todo in the backend
function updateTodoOnServer(todoId, newTodoData) {
  // This represents your API call to the server
  return fetch(`/api/todos/${todoId}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newTodoData),
  }).then((res) => res.json());
}
This code snippet imports the `useMutation` hook from TanStack (specifically, the React Query library) and defines a function `updateTodoOnServer`, which simulates updating a todo item on the server via an API call using the Fetch API.
const { mutate } = useMutation(
  ({ todoId, newTodoData }) => updateTodoOnServer(todoId, newTodoData),
  {
    // onMutate is triggered before the mutation function is called
    onMutate: async ({todoId, newTodoData}) => {
      // Cancel any outgoing refetches
      await queryClient.cancelMutations();

      // Snapshot the previous value
      const previousTodos = queryClient.getQueryData(['todos']);

      // Optimistically update to the new value
      queryClient.setQueryData(['todos'], (old) => ({
        ...old,
        [todoId]: {...old[todoId], ...newTodoData},
      }));

      return { previousTodos };
    },
    // If the mutation fails, use the context returned from onMutate to rollback
    onError: (err, variables, context) => {
      queryClient.setQueryData(['todos'], context.previousTodos);
    },
    // Always refetch after error or success
    onSettled: () => {
      queryClient.invalidateQueries(['todos']);
    },
  }
);
This snippet demonstrates how to use the `mutate` function from the useMutation hook to perform optimistic updates. When `mutate` is called (e.g., after a user action), it updates the UI optimistically with `onMutate` by setting new data for the todo item directly in the cache before the server responds. If the update fails, the `onError` function rolls back to the previous state. After the mutation succeeds or fails, `onSettled` ensures the relevant data is refetched from the server.