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.
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.**
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:
- When
startTransition
begins,isPending
is automatically set totrue
. - When
startTransition
completes,isPending
is automatically set tofalse
.
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>
);
}
Live Demo:
2. useActionState
💡 A hook that consolidates standard form processing, making it much simpler. Likely to become a best practice.
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)).
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
);
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>
);
}
💡 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.
Live Demo:
3. <form>
Action
💡 You can pass a custom function to the `<form>` action. Consider this if `useActionState` is not used.
As already seen in the useActionState
example, a function can be passed to the <form>
action.
<form action={actionFunction}>
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>
);
}
Live Demo:
4. useFormStatus
💡 Retrieve `<form>` progress status within child components without prop drilling.
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>
);
}
Live Demo:
5. useOptimistic
💡 Quickly set "temporary display values" during asynchronous processing.
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>
);
}
Live Demo:
6. use
💡 A simple function-like hook. Works like `useMessage` when calling `use(message)`. It only fetches values during rendering.
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>
);
}
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>
);
}
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.
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>
);
}
- Reference: Features included in the React Team's Full Stack Architecture Vision Official Documentation
Live Demo:
9. Server Functions
💡 Define functions that execute only on the server.
(Up until September 2024, all server functions were called ServerActions.)
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();
}
"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>
);
}
Live Demo:
10. ref
via Props
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>
);
}
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
};
}}
/>