Blog>
Snippets

Optimistic UI Updates with React Query

Utilize React Query to fetch, cache and update the UI optimistically after mutations for an instantaneous user experience.
import { useQuery, useMutation, useQueryClient } from 'react-query';

function updateItem(id, newItem) {
  // This function would update the item on the server
  return fetch(`/api/items/${id}`, {
    method: 'PUT',
    body: JSON.stringify(newItem),
    headers: {
      'Content-Type': 'application/json',
    },
  }).then(res => res.json());
}

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

  return useMutation(updateItem, {
    onMutate: async (newItem) => {
      await queryClient.cancelQueries('items');

      const previousItems = queryClient.getQueryData('items');

      queryClient.setQueryData('items', old => old.map(item => {
        return item.id === newItem.id ? {...item, ...newItem} : item;
      }));

      return { previousItems };
    },
    onError: (err, newItem, context) => {
      queryClient.setQueryData('items', context.previousItems);
    },
    onSettled: () => {
      queryClient.invalidateQueries('items');
    },
  });
}
This code snippet defines a custom hook using React Query that enables optimistic UI updates. First, it imports necessary hooks from 'react-query'. The `updateItem` function sends an update request to the server. The `useOptimisticUpdate` custom hook uses `useMutation` to perform the update optimistically. On mutation, it cancels any ongoing queries for 'items', stores the previous state, and immediately updates the local cache to reflect the new state, simulating an instant update. If the mutation fails, the onError callback restores the previous state. Regardless of success or error, onSettled will either refetch 'items' or ensure the state is consistent with the server.
import { useQuery } from 'react-query';

function ItemsList() {
  const { data: items, isLoading } = useQuery('items', fetchItems);

  if (isLoading) return 'Loading...';

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}
This code snippet is responsible for fetching and displaying a list of items using React Query's useQuery hook. `ItemsList` is a functional component that fetches item data with a `fetchItems` function, indicating loading state, and renders the list of items. It interacts with the React Query cache provided by the `useOptimisticUpdate` hook defined in the previous code snippet. Whenever an item is updated optimistically, this component will re-render with the updated data immediately, providing an instant feedback loop to the user.
function ItemUpdater({ item }) {
  const { mutate } = useOptimisticUpdate();

  const handleUpdate = (updatedFields) => {
    const newItem = { ...item, ...updatedFields };
    mutate(newItem);
  };

  // Assuming there's a form or input fields to capture the update
  return (
    // JSX to render form elements and invoke handleUpdate
  );
}
This snippet of code outlines a component `ItemUpdater` that allows a user to update an item. It uses the `useOptimisticUpdate` custom hook to get the `mutate` function, enabling a mutation to be triggered when `handleUpdate` is called. The mutation is optimistically applied to the UI by the custom hook before server confirmation. Once a user updates the item (e.g., through a form), `handleUpdate` function is invoked with the updated fields, and the UI will immediately reflect these changes, leading to a responsive experience.