React is all about building responsive and interactive user interfaces, and with the release of React 18, developers have even more tools at their disposal to achieve this goal. One such tool is the useOptimistic hook, a powerful addition to React's arsenal of state management utilities. In this blog post, we'll explore what useOptimistic is, how it works, and how you can leverage it to enhance the user experience in your applications.

Understanding useOptimistic

At its core, the useOptimistic hook is designed to improve the perceived performance and responsiveness of applications by updating the UI optimistically in response to user actions. It allows developers to provide immediate feedback to users while asynchronously updating the underlying state.


How does useOptimistic work?

To understand how useOptimistic works, let's consider a common scenario: submitting a form. Typically, when a user submits a form, the application sends a request to the server to process the data. While waiting for the server's response, the UI might appear unresponsive, leading to a less-than-ideal user experience.

With useOptimistic, you can update the UI immediately after the user initiates the form submission, providing instant feedback. Then, in the background, the application sends the request to the server and updates the UI again based on the server's response.


Definition

Following the official React documentation at https://react.dev/reference/react/useOptimistic they said:

Synctax: useOptimistic(state, updateFn) 


useOptimistic is a React Hook that lets you show a different state while an async action is underway. It accepts some state as an argument and returns a copy of that state that can be different during the duration of an async action such as a network request. You provide a function that takes the current state and the input to the action, and returns the optimistic state to be used while the action is pending.
This state is called the “optimistic” state because it is usually used to immediately present the user with the result of performing an action, even though the action actually takes time to complete.


Parameters 

  • state: the value to be returned initially and whenever no action is pending.
  • updateFn(currentState, optimisticValue): a function that takes the current state and the optimistic value passed to addOptimistic and returns the resulting optimistic state. It must be a pure function. updateFn takes in two parameters. The currentState and the optimisticValue. The return value will be the merged value of the currentState and optimisticValue.

Returns 

  • optimisticState: The resulting optimistic state. It is equal to state unless an action is pending, in which case it is equal to the value returned by updateFn.
  • addOptimisticaddOptimistic is the dispatching function to call when you have an optimistic update. It takes one argument, optimisticValue, of any type and will call the updateFn with state and optimisticValue.



import { useOptimistic } from 'react';

function AppContainer() {
  const [optimisticState, addOptimistic] = useOptimistic(

    state,

    // updateFn

    (currentState, optimisticValue) => {

      // merge and return new state

      // with optimistic value

    }

  );
}


Example Usage

In this example, we'll implement a "like" functionality for each message, where users can click a button to like a message. We'll use the useOptimistic hook to immediately update the UI when a user likes a message, without waiting for confirmation from the server.


import React, { useState, useRef } from "react";

// Mock function to deliver a like to the server
async function deliverLike(messageId) {
  // Simulating server delay
  await new Promise((resolve) => setTimeout(resolve, 1000));
  // Assuming the server returns the updated message with the like count
  return { id: messageId, text: `Message ${messageId}`, likes: 1 };
}

function Thread({ messages }) {
  const formRef = useRef();

  // Function to handle message likes
  async function handleLike(messageId) {
    // Optimistically update UI
    addOptimisticLike(messageId);
    // Send like to server
    await deliverLike(messageId);
  }

  // Use of useOptimistic hook to manage message state
  const [optimisticMessages, addOptimisticLike] = useOptimistic(
    messages,
    (state, messageId) =>
      state.map((message) =>
        message.id === messageId
          ? { ...message, likes: message.likes + 1 }
          : message
      )
  );


  return (
    <>
      {optimisticMessages.map((message) => (
        <div key={message.id}>
          <p>{message.text}</p>
          <button onClick={() => handleLike(message.id)}>
            Like ({message.likes})
          </button>
        </div>
      ))}
    </>
  );
}


function App() {
  const [messages, setMessages] = useState([
    { id: 1, text: "Message 1", likes: 0 },
    { id: 2, text: "Message 2", likes: 0 },
    { id: 3, text: "Message 3", likes: 0 },
  ]);


  return <Thread messages={messages} />;
}

// useOptimistic mock implementation for illustration purpose
function useOptimistic(initialState, updateFunction) {
  const [state, setState] = useState(initialState);

  const addOptimisticChange = (id) => {
    setState((prevState) => updateFunction(prevState, id));
  };

  return [state, addOptimisticChange];
}

export default App;


Explain the example above:

  • We have a Thread component that displays a list of messages. Each message has a "Like" button associated with it.
  • When a user clicks the "Like" button, the handleLike function is invoked. This function first updates the UI optimistically by incrementing the like count of the corresponding message. Then, it sends the like to the server asynchronously using the deliverLike function.
  • We use the useOptimistic hook to manage the state of the messages. It takes the initial state of the messages and a function that describes how to update the state optimistically. In this case, when a message is liked, it increments the like count of the corresponding message in the state.
  • The App component manages the main state of the application, which includes the list of messages and their respective like counts.
  • Each message rendered in the Thread component displays its text and the current number of likes. When a user clicks the "Like" button, the like count is immediately updated on the UI, providing instant feedback to the user.


Following the example above we demonstrates how the useOptimistic hook can be used to improve the perceived performance and responsiveness of a React application by updating the UI optimistically in response to user actions. This hook is really useful right?


Note

The useOptimistic Hook is currently only available in React’s Canary and experimental channels. Learn more about React’s release channels here.


Conclusion

The useOptimistic hook in React 18 is a powerful tool for improving the perceived performance and responsiveness of your applications. By updating the UI optimistically in response to user actions, you can provide a smoother and more interactive user experience. Whether you're submitting forms, updating data, or performing other asynchronous tasks, useOptimistic can help you create applications that feel fast and responsive, delighting users and driving engagement.