Improve Asynchronous State UX in React with useOptimistic

Handling asynchronous states is important for creating a seamless user experience (UX). In React, there are many solutions to handling asynchronous states. While all them functionally work, some solutions provide a better user experience. Let’s look at a few of the approaches for handling async operations in React.

Naive Approach

The Naive Approach to asynchronous state management is for the client to make a request to the server, and then to wait for a response. During this time no feedback is given to the user, with the UI remaining static. Only when the client receives a response is there any indication that something has happened.

Problems with the Naive Approach

  • User Frustration: Without feedback, users might believe their requests have failed
  • Rage Clicks: Users may click repeatedly, resulting in multiple requests. This is fine for GET requests, but can cause issues for POST requests, like submitting the same form multiple times

Basic Approach

A simple approach to solve the rage click issue from the Naive Approach is introducing some state to the component handling the requests. Disabling a form when a request is in flight and providing a disabled visual state is the first step towards providing users feedback about their actions towards the server.

The UX experience is better with this approach, but users still must wait for requests to finish.

Advanced Approach

One approach that lets developers to make server requests appear to be instant is the usage of Optimistic Updates. When this method is used, the UI shows a presumed server response before a request actually resolves.

Optimistic Updates are only good for certain cases. Operations that are likely to succeed, where the client already has access to the data needed to show the information that will be stored on the server.

The simplest example is adding items to a list stored on a server. Even if there is server-side processing, the client still has access to all of the data that will be associated with the list entry. The only case where the user knows there was an issue or a failed request is if the server response is abnormal.

React’s UseOptimistic Hook

The advanced approach of Optimistic Updates provides a nice user experience. The developer experience of implementing Optimistic Updates with basic React is not amazing. Multiple pieces of state must be managed which leads to significant amounts of boilerplate code. The status of the “in-flight” request must be manually tracked. What is temporarily shown and if or when the actual response is shown, developers are also responsible for managing.

React 19’s useOptimistic hook helps reduces these annoyances by providing a built-in hook for Optimistic Updates. The hook allows developers to manage optimistic states declaratively.

By reducing the amount of brain cache required for state management it allows for devs to spend more time focusing on the overall user experience.

Example of Optimistic Updates Using UseOptimistic

function GroceryList() {
  const [Items, setItems] = useState(["apple", "banana", "cherry"]);
  const [optimisticItems, setOptimistic] = useOptimistic(Items);

  const addItems = async (newItems) => {
    // Apply the optimistic update
    setOptimistic((currentItems) => [...currentItems, newItems]);

    try {
      // Send the new Items to the server
      const response = await fetch('/api/items', {
        method: 'POST',
        body: JSON.stringify(newItems),
      });

      if (!response.ok) {
        throw new Error('Failed to add Items');
      }
      const savedItems = await response.json();

      // Update with the confirmed server response
      setItems((currentItems) => [...currentItems, savedItems]);
    } catch (error) {
      // Handle errors by rolling back optimistic changes
      setOptimistic((currentItems) =>
        currentItems.filter((items) => items !== newItems)
      );
      console.error('Error adding items:', error);
    }
  };

  return (
    <div>
      <ul>
        {optimisticItems.map((items) => (
          <li key={items.id}>{items.text}</li>
        ))}
      </ul>
    </div>
  );
}
Conversation

Join the conversation

Your email address will not be published. Required fields are marked *