React v19 Sample Code Examples

VTeacher - Feb 25 - - Dev Community

React 19 Sample Code

Regarding React 19, the RC version was announced in April 2024, and the stable version was released in December of the same year. Since more than two months have passed, it is no longer new, but I have compiled this article for organizational purposes.

Official Blog Post

Repository (GitHub)

I have prepared sample code based on Next.js (version 15).

Live Demo (Vercel)

The sample code has been deployed to Vercel, so feel free to check it out.

Explanation of Sample Code


1. useTransition

💡 **Improvement in v19 → `isPending` is automatically set to `true` when `startTransition` begins.**
Enter fullscreen mode Exit fullscreen mode

useTransition Documentation

useTransition was introduced in v18, but from v19, it now supports asynchronous functions within transitions, making it easier to handle pending states, errors, form submissions, and optimistic updates automatically.

Behavior:

  1. When startTransition begins, isPending is automatically set to true.
  2. When startTransition completes, isPending is automatically set to false.

This eliminates the need to manually manage the isPending state, resulting in cleaner asynchronous handling.

import { useRouter, usePathname } from "next/navigation";
import { useState, useTransition } from "react";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve('sample:' + name), 3000));
}

export default function Note() {
    const [isPending, startTransition] = useTransition();
    const [name, setName] = useState("");
    const [sample, setSample] = useState<string | null>(null);
    const router = useRouter();
    const pathname = usePathname();

    const handleSubmit = () => {
        startTransition(async () => { // 👈 isPending automatically becomes true
            const result = await fetchSample(name);
            if (result.includes("error")) {
                setSample(result);
                return;
            }
            router.push(pathname);
        }); // 👈 isPending automatically becomes false
    };

    return (
        <div className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                Calling `startTransition` alone sets `isPending` to `true`. This allows for a clean way to handle asynchronous operations. While this is a fundamental process, the following chapters will focus on form-specific optimizations, introducing even more concise and simplified approaches.
            </p>
            <input type="text" className="border p-2 rounded" value={name} placeholder="name" onChange={(e) => setName(e.target.value)} />
            <button onClick={handleSubmit} disabled={isPending} className="bg-blue-500 text-white p-2 rounded">
                {isPending ? "Pending..." : "Post"}
            </button>
            {sample && <p>{sample}</p>}
            {isPending && <span className="w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin inline-block"></span>}
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


2. useActionState

💡 A hook that consolidates standard form processing, making it much simpler. Likely to become a best practice.
Enter fullscreen mode Exit fullscreen mode

useActionState Documentation

useActionState is a specialized hook for forms that simplifies common form handling (even more than useTransition). It is recommended to use useActionState as the first choice for form handling.

💡 In the Canary version, this was called `useFormState`, but it was renamed ([#28491](https://github.com/facebook/react/pull/28491)).
Enter fullscreen mode Exit fullscreen mode

How to use useActionState

const [
    state,        // The state of the form, which stores the return value of formAction
    formAction,   // A wrapped function that is triggered when the form is submitted
    isPending     // A flag that indicates whether the form is being submitted
] = useActionState(
    fn,           // The logic inside formAction (server functions are supported)
    initialState, // Initial state of the form
    permalink?    // Optional, needed if fn is a server function
);
Enter fullscreen mode Exit fullscreen mode
import { useActionState } from "react";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve(name ?? ""), 3000));
}

export default function Note() {
    const [message, submitAction, isPending] = useActionState<string | null, FormData>(
        async (_prevState: string | null, formData: FormData) => { 
            const resMessage = await fetchSample(formData.get("name") as string | null);
            return resMessage;
        },
        null
    );

    return (
        <form action={submitAction} className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                This hook simplifies standard form processing. Using `useActionState` makes form handling much cleaner and more concise.
            </p>
            <input type="text" className="border p-2 rounded" name="name" placeholder="name" />
            <button type="submit" disabled={isPending} className="bg-blue-500 text-white p-2 rounded">
                {isPending ? "Pending..." : "Post"}
            </button>
            {isPending && <span className="w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin inline-block"></span>}
            {message && <p>{message}</p>}
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode
💡 Using `useActionState` with server functions allows the form response to be displayed even if the form is submitted before hydration is complete. If using a server function, check the official documentation for permalink settings.
Enter fullscreen mode Exit fullscreen mode

Live Demo:


3. <form> Action

💡 You can pass a custom function to the `<form>` action. Consider this if `useActionState` is not used.
Enter fullscreen mode Exit fullscreen mode

Form Actions Documentation

As already seen in the useActionState example, a function can be passed to the <form> action.

<form action={actionFunction}>
Enter fullscreen mode Exit fullscreen mode

This is not limited to useActionState; you can pass any custom-defined function. The form values can be accessed via formData. This approach is useful when the standard useActionState template is not sufficient.

import { useState } from "react";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve(name ?? ""), 3000));
}

export default function Note() {
    const [message, setMessage] = useState("");

    // Function to be passed to <form> action
    async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        const resMessage = await fetchSample(formData.get("name") as string);
        setMessage(resMessage);
    }

    return (
        <form onSubmit={handleSubmit} className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                This method connects custom functions with a form. Form values can be accessed via `formData`. It is useful when `useActionState` cannot handle a case or when standard processing is not sufficient.
            </p>
            <input type="text" className="border p-2 rounded" name="name" placeholder="name" required />
            <button type="submit" className="bg-blue-500 text-white p-2 rounded">
                Post
            </button>
            <p>{message}</p>
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


4. useFormStatus

💡 Retrieve `<form>` progress status within child components without prop drilling.
Enter fullscreen mode Exit fullscreen mode

useFormStatus Documentation

This hook is designed with UI design and component structuring in mind. Instead of prop drilling isPending through multiple levels, useFormStatus allows child components to retrieve the form status directly.

import { useFormStatus } from "react-dom";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve(name ?? ""), 3000));
}

// 👇 Retrieve `<form>` status inside a child component
function SubmitButton() {
    const { pending } = useFormStatus();
    return <button type="submit" disabled={pending} className="bg-blue-500 text-white p-2 rounded">{pending ? "Posting..." : "Post"}</button>;
}

export default function Note() {
    async function handleSubmit(formData: FormData) {
        const name = formData.get("name") as string | null;
        await fetchSample(name);
    }

    return (
        <form action={handleSubmit} className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                You can retrieve the form state within a child component. There is no need to pass props like `isPending`.
            </p>
            <input type="text" className="border p-2 rounded" name="name" placeholder="name" required />
            <SubmitButton />
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


5. useOptimistic

💡 Quickly set "temporary display values" during asynchronous processing.
Enter fullscreen mode Exit fullscreen mode

useOptimistic Documentation

This hook allows setting a temporary optimistic value while waiting for an asynchronous process to complete. It renders even faster than useState.

import { useOptimistic, startTransition } from "react";
import { useState } from "react";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve(name ?? ""), 3000));
}

export default function Note() {
    const [name, setName] = useState<string | null>(null);
    const [optimisticName, setOptimisticName] = useOptimistic<string | null>(name);

    async function handleUpdate() {
        startTransition(async () => {
            setOptimisticName("■■■"); // 👈 Updates faster than `setName()`
            const result = await fetchSample("Bob");
            setName(result);
        });
    }

    return (
        <div className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                You can instantly display values you want to show for now (optimistic updates). It renders even faster than the timing of `useState`.
            </p>
            <button onClick={handleUpdate} className="bg-blue-500 text-white p-2 rounded">
                Show Name
            </button>
            <p className="mt-2">{optimisticName}</p>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


6. use

💡 A simple function-like hook. Works like `useMessage` when calling `use(message)`. It only fetches values during rendering.
Enter fullscreen mode Exit fullscreen mode

use Documentation

Similar to hooks, use can only be called during rendering. Unlike some hooks, use can be used inside conditional branches.

A common misconception is that use is an alternative to await, but use works only during rendering.

"use client";

import { Suspense, use } from "react";

function Message({ messagePromise }: { messagePromise: Promise<string> }) {
    const message = use(messagePromise);
    return <p>Here is the message: {message}</p>;
}

export default function Note({ messagePromise }: { messagePromise: Promise<string> }) {
    return (
        <div className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                Normally, `fetch` is used in server components, but using `await` here blocks rendering and prevents effective use of `Suspense`. However, with the introduction of `use`, passing a `Promise` directly to a client component has become a smart solution, making it easier to control loading states using `Suspense`.
            </p>
            <Suspense fallback={<p>Waiting for message...</p>}>
                <Message messagePromise={messagePromise} />
            </Suspense>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


7. useActionState and useOptimistic

An example that combines useActionState and setOptimisticName.

import { useActionState, useOptimistic, useState } from "react";

async function fetchSample(name: string | null): Promise<string> {
    return new Promise((resolve) => setTimeout(() => resolve(name ?? ""), 3000));
}

export default function Note() {
    const [name, setName] = useState("");
    const [optimisticName, setOptimisticName] = useOptimistic<string | null>("");

    const [error, submitAction, isPending] = useActionState<string | null, FormData>(
        async (_prevState: string | null, formData: FormData) => {
            const newName = formData.get("name") as string | null;
            if (newName === '') {
                return "Error: required";
            }
            setOptimisticName(newName);
            await fetchSample(newName);
            setName(newName ?? "");
            return null;
        },
        null
    );

    return (
        <form action={submitAction} className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                An example combining `useActionState` and `setOptimisticName`.
            </p>
            <p className="mt-2">Name: {optimisticName || name}</p>
            <input type="text" name="name" placeholder="name" className="border p-2 mb-2 rounded" />
            <button type="submit" disabled={isPending} className="bg-blue-500 text-white p-2 rounded">
                {isPending && <span className="w-4 h-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin inline-block" />}
                Post
            </button>
            {error && <p className="text-red-500">{error}</p>}
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


8. React Server Components

💡 Server Components have been widely discussed, but they are officially introduced as a new component type in React 19. Unlike SSR, they are rendered ahead of time before bundling.
Enter fullscreen mode Exit fullscreen mode

React Server Components Documentation

By default, components are Server Components. Check that users is not included in the source code. In Chrome, right-click and select "Inspect" to verify the source. Look at the file:

http://localhost:3000/_next/static/chunks/app/lesson08-react-server-components/page.js.

If you add "use client", the users data will be included, exposing private information such as suzuki@example.com.

// "use client"; // Note: Adding this makes it a Client Component.

const users = [
    {
        id: "1",
        name: "Suzuki",
        email: "suzuki@example.com",
    },
    {
        id: "2",
        name: "Satou",
        email: "tanaka@example.com",
    },
];

export default function Page() {
    return (
        <div className="text-center m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                By default, this is a Server Component. Check that `users` is not included in the source code. In Chrome, right-click and inspect the file at:
                `http://localhost:3000/_next/static/chunks/app/lesson08-react-server-components/page.js`. If `"use client"` is added, the `users` data will be included, exposing private information.
            </p>
            <h1>This is server side.</h1>
            <p>users: {users.length}</p>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


9. Server Functions

💡 Define functions that execute only on the server.  
(Up until September 2024, all server functions were called ServerActions.)
Enter fullscreen mode Exit fullscreen mode

Server Functions Documentation

This allows calling functions that can only execute on the server from UI events.

The following example displays the server uptime when a button is clicked.

"use server";

import { exec } from "child_process";
import { promisify } from "util";

const stat = promisify(exec) as (command: string) => Promise<{ stdout: string; stderr: string }>;

export async function action(): Promise<string> {
    const { stdout } = await stat("uptime");
    return stdout.trim();
}
Enter fullscreen mode Exit fullscreen mode
"use client";

import { action } from "../actions";

export default function Note() {
    async function handleClick() {
        const stat = await action();
        alert(stat);
    }

    return (
        <div className="m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                You can call functions that execute only on the server from events like clicks. When you click the button, the server uptime will be displayed.
            </p>
            <button onClick={handleClick} className="bg-blue-500 text-white p-2 rounded">
                Server Action
            </button>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Live Demo:


10. ref via Props

Ref as a Prop Documentation

One of the improvements in React 19 is that refs can now be accessed as props.

(This means forwardRef may become deprecated in the future.)

"use client";

import { useRef, forwardRef } from "react";

const ChildInputPropRef = ({ placeholder, ref }: { placeholder: string, ref: React.Ref<HTMLInputElement> }) => (
    <input ref={ref} type="text" placeholder={placeholder} className="border p-2 rounded" />
);

const ChildInputForwardRef = forwardRef<HTMLInputElement, { placeholder: string }>(
    ({ placeholder }, ref) => (
        <input ref={ref} type="text" placeholder={placeholder} className="border p-2 rounded" />
    )
);

ChildInputForwardRef.displayName = "ChildInputForwardRef";

export default function Note() {
    const inputForwardRef = useRef<HTMLInputElement>(null);
    const inputPropRef = useRef<HTMLInputElement>(null);

    return (
        <div className="flex flex-col items-center gap-4 p-4 m-2">
            <p className="text-xs text-gray-500 bg-white my-2">
                One of the improvements in React 19 is that refs can be accessed as props. `forwardRef` is no longer needed.
            </p>
            <input type="text" placeholder="Normal" className="border p-2 rounded" />
            <ChildInputForwardRef placeholder="ForwardRef" ref={inputForwardRef} />
            <ChildInputPropRef placeholder="PropRef" ref={inputPropRef} />
            <button
                onClick={() => inputForwardRef.current?.focus()}
                className="bg-blue-500 text-white p-2 m-2 rounded"
            >
                ForwardRef focus()
            </button>
            <button
                onClick={() => inputPropRef.current?.focus()}
                className="bg-blue-500 text-white p-2 m-2 rounded"
            >
                PropRef focus()
            </button>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Another improvement is that refs now support cleanup similar to useEffect:

<input
  ref={(ref) => {
    // Ref created

    // NEW: Return a cleanup function to reset
    // the ref when the element is removed from the DOM.
    return () => {
      // Ref cleanup
    };
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Live Demo:

. . . . . .