Optimistic Update with Rollback on Error

Show how to perform an optimistic update to the UI based on a mutation, with a rollback mechanism in case the mutation fails, ensuring data integrity and continuous user experience.
import { useMutation, useQueryClient } from 'react-query';

const updateTodo = async (todo) => {
  // simulated API call to update a todo
  return fetch('', {
    method: 'POST',
    body: JSON.stringify(todo),
    headers: { 'Content-Type': 'application/json' }
  }).then(res => res.json());
Defines an asynchronous function updateTodo that simulates an API call for updating a todo item. It utilizes the fetch API to send a POST request to a hypothetical endpoint. This function will be used in the mutate function for the optimistic update.
const useOptimisticUpdateTodo = () => {
  const queryClient = useQueryClient();

  return useMutation(updateTodo, {
    onMutate: async (newTodo) => {
      // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
      await queryClient.cancelQueries('todos');

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

      // Optimistically update to the new value
      queryClient.setQueryData('todos', old => => === ? { ...todo, ...newTodo } : todo));

      return { previousTodos };
    onError: (err, newTodo, context) => {
      // Rollback to the previous value
      queryClient.setQueryData('todos', context.previousTodos);
    onSettled: () => {
      // After mutation or rollback, refetch the todos query
This custom hook, useOptimisticUpdateTodo, utilizes React Query's useMutation hook to perform the optimistic update. It first cancels any outgoing refetches to prevent overwriting the optimistic update. On a mutation, it takes a snapshot of the current todos, performs the optimistic update, and in the event of an error, rolls back to the previous state using the snapshot. After mutation or rollback, it ensures the query is refetched to maintain data integrity.