DEV Community

Cover image for React 19 example of the useOptimistic() hook
Simone Gentili
Simone Gentili

Posted on

React 19 example of the useOptimistic() hook

A working example

This post is related to this example. You can see just the code or read this article that explain step by step what's happening in the little app.

Create new react app

First of all create new React application. If you like vite, you can use the command below.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Experimental stuffs

Meanwhile I am writing the hook is still experimental. In the canary version of React 19 so... we need more packages to add the hook to the project.

npm install react@^0.0.0-experimental-6f23540c7d-20240528 \
react-dom@^0.0.0-experimental-6f23540c7d-20240528 \
uuid@^9.0.1 \
@types/react@18.3 \
@types/react-dom@18.3
Enter fullscreen mode Exit fullscreen mode

Import

The import is obviously...

import { useOptimistic, useState } from "react";
Enter fullscreen mode Exit fullscreen mode

Data

In this example we will treat books. Because this example will be part of a book I am writing (italian only) about Reacct 19.

type Book = { text: string; sending: boolean; key?: number };
Enter fullscreen mode Exit fullscreen mode

Rest call

At some point The little example will call some api. For simplicity I am simulating network delay with a Promise. Also, I simulate the the real call will update a value adding " content from rest api" like a rest api is returning data updated/filtered/fixed...

const createNewBook = async (message: FormDataEntryValue | null) => {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return message + " content from rest api";
};
Enter fullscreen mode Exit fullscreen mode

App

Here we are!! The application contains a listo of books. React, TypeScript and Next.js is already written. React... is a working progress... The App create a list of books. A function that send data to an api and then add the result to the books.

export default function App() {
  const [books, setBooks] = useState<Book[]>([
    { text: "React", sending: false, key: 1 },
    { text: "React, TypeScript e Next.js", sending: false, key: 3 },
  ]);

  async function sendMessage(formData: FormData) {
    const sentMessage = await createNewBook(formData.get("message"));
    setBooks((messages: any) => [...messages, { text: sentMessage }]);
  }

  return <Library books={books} createBook={sendMessage} />;
}
Enter fullscreen mode Exit fullscreen mode

Library

The main point of this article is the component <Library />. Let's take a step back. The data we are working with are book. And books are stored in this way:

type Book = { text: string; sending: boolean; key?: number };
Enter fullscreen mode Exit fullscreen mode

Whenever sending is true, <small> (Sending...)</small> will appear after the new book. And sending is false in books array we have seen before.

  const [books, setBooks] = useState<Book[]>([
    { text: "React", sending: false, key: 1 },
    { text: "React, TypeScript e Next.js", sending: false, key: 3 },
  ]);
Enter fullscreen mode Exit fullscreen mode

We also add a form to add a new title to the book list.

function Library(/** some params */) {
  /** some contents */

  return (
    <>
      {optimisticContent.map(
        (message: { text: string; sending: boolean }, index: number) => (
          <div key={index}>
            {message.text}
            {!!message.sending && <small> (Sending...)</small>}
          </div>
        )
      )}

      <form action={formAction}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Form action

Form will send data to formAction() function. Then the function add the content to the book list and send form to an external api. (use the imagination about the external api).

  async function formAction(formData: FormData) {
    addContent(String(formData.get("message")));
    await sendMessage(formData);
  }
Enter fullscreen mode Exit fullscreen mode

useOptimistic

With this syntax we configure optimisticContent as optimistic content. For example, when addContent method is called, he receive new book marking the Book with sending=true.

  const [optimisticContent, addContent] = useOptimistic(
    messages,
    (state: Book[], newMessage: string) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );
Enter fullscreen mode Exit fullscreen mode

Complete code

function Library({
  books: messages,
  createBook: sendMessage,
}: {
  books: Book[];
  createBook: (formData: FormData) => void;
}) {
  async function formAction(formData: FormData) {
    addContent(String(formData.get("message")));
    await sendMessage(formData);
  }

  const [optimisticContent, addContent] = useOptimistic(
    messages,
    (state: Book[], newMessage: string) => [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );

  return (
    <>
      {optimisticContent.map(
        (message: { text: string; sending: boolean }, index: number) => (
          <div key={index}>
            {message.text}
            {!!message.sending && <small> (Sending...)</small>}
          </div>
        )
      )}

      <form action={formAction}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Resume

When you use a form to send a new Book to the list of books, the formAction method add the item into the optimistic list. Then send data to an api, for example. After a while, the api return a value. And then the value is added to the real list of books.

Top comments (0)