DEV Community

Makoto Tsuga
Makoto Tsuga

Posted on

useOptimistic in React

I introduce "useOptimistic" which is new hook in React19.
Note: As of July 2024, this hook is Canary version. Hence, it does not ensure it is stable so hesitate to use in production environment.

What is useOptimistic in React?

useOptimistic is a React hook that displays a different state only while some asynchronous action is in progress.

A use case for useOptimistic is when you want the UI to update before the state in the database is updated. This is often used in message App. For example, when a user sends a comment, it is processed by the database. During this processing, the UI updates to display the comment. If there is an error in the database processing, the comment is removed (reverted to the state before the processing).

Example of message App by using useOptimistic.

Let's consider the previous example.

①When a user makes a comment and clicks the send button, a form action is executed.
For those unfamiliar with form actions, please read this article first.

Image description

②The state of useOptimistic is updated by the function in useOptimistic.

Image description

③original state is updated
Image description

④Finish the Form Action and copy original state

Image description

In this way, useOptimistic can hold a temporary state and reflect it in the UI.

How to use it.

  const [optimisticState, addOptimistic] = useOptimistic(
    originalState,
    // updateFn
    (currentState, optimisticValue) => {
      // merge and return new state
      // with optimistic value
    }
  );
Enter fullscreen mode Exit fullscreen mode
  • optimisticState: This is a state that holds temporary information and, except during server action processing, it will have the same value as the original state.

  • addOptimistic: This is a function called to update the optimisticState. It is called during server action processing and and allows for passing arguments at that time.

  • originalState: This is the original state. This value is expected to be displayed in the initial state and outside of server action execution.

  • updateFn(currentState, optimisticValue): This function receives the current state and the arguments passed to addOptimistic, and performs the update process for optimisticState.

Note:
Calling addOptimistic within a server action triggers UseOptimistic. At that time, arguments can be passed.

//Server Action

  const formAction = async (formData: FormData) => {
    addOptimistic(formData.get("message")); //update useOptimistic state
    formRef.current!.reset();
    //Assumed server-side processing(Data based)
    await sendMessage(formData); //update original state
  };
Enter fullscreen mode Exit fullscreen mode

Example of the codes

page.tsx

"use client";
import UseOptimistic from "././components/UseOptimistic";
import { useState } from "react";

export default function Home() {
  //useState(original state)
  const [originalState, setOriginalState] = useState([
    { text: "Hello there!", sending: false, key: 1 },
  ]);

  //function update original state
  const sendMessage = async (formData: FormData) => {
    await new Promise((res) => setTimeout(res, 2000));
    const sentMessage = formData.get("message") as string;
    setOriginalState((originalState) => [
      ...originalState,
      { text: sentMessage, sending: false, key: originalState.length + 1 },
    ]);
  };

  return (
    <UseOptimistic originalState={originalState} sendMessage={sendMessage} />
  );
}

Enter fullscreen mode Exit fullscreen mode

In this file, I have a original state and function to update original state. And I pass them to the component of UseOptimistic.tsx as props.

UseOptimistic.tsx

import { useOptimistic, useRef } from "react";

interface ThreadProps {
  originalState: { text: string; sending: boolean; key: number }[];
  sendMessage: (formData: FormData) => void;
}

const UseOptimistic = ({ originalState, sendMessage }: ThreadProps) => {
  const formRef = useRef<HTMLFormElement>(null);

  //Server Action
  const formAction = async (formData: FormData) => {
    addOptimistic(formData.get("message")); //update useOptimistic state
    formRef.current!.reset();
    //Assumed server-side processing(Data based)
    await sendMessage(formData); //update original state
  };
  const [optimisticState, addOptimistic] = useOptimistic(
    originalState,
    (currentState, optimisticValue) => [
      ...currentState,
      {
        text: optimisticValue as string,
        sending: true,
        key: currentState.length + 1,
      },
    ]
  );

  return (
    <>
      {optimisticState.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
};

export default UseOptimistic;

Enter fullscreen mode Exit fullscreen mode

In this file, I have a optimisticState and function to update this state by using useOptimistic. And I have a server action(form action). When user click the button, formAction is called and update the optimisticState. During the this operation, we can see the new text with "Sending". After execution of formAction, optimisticState has original state. Hence, we can not see "Sending". However, In form action, I call the sendMessage and update original state so I can see new text without "sending".

Image description

Top comments (0)