Chapter 33 β React 19: Actions, use & Server Components
π Definition
React 19 (stable since December 2024) adds first-class support for async data mutations β Actions β along with new hooks (useActionState, useOptimistic, useFormStatus, use), stable Server Components, ref as a regular prop, and built-in document metadata. The React Compiler (opt-in) automatically memoises components.
π What Changed and Why It Matters
| Feature | Replaces / improves |
|---|---|
Actions (async functions in startTransition or <form action>) |
Manual isLoading, error state and e.preventDefault() boilerplate |
useActionState |
useState + useReducer around a submit handler |
useOptimistic |
Hand-written optimistic update + rollback logic |
useFormStatus |
Prop-drilling isSubmitting into a submit button |
use(promise) / use(context) |
Reading a promise inside render with Suspense; reading context conditionally |
ref as a prop |
forwardRef |
<title>, <meta>, <link> in components |
react-helmet |
| Server Components + Server Actions | Separate API route for every form or read |
| React Compiler | Most manual useMemo / useCallback / React.memo |
π» Code Example β Form With an Action and useActionState
import { useActionState } from "react";
async function updateName(prevState, formData) {
const name = formData.get("name");
if (!name) return { error: "Name is required" };
const res = await fetch("/api/me", { method: "PATCH", body: JSON.stringify({ name }) });
if (!res.ok) return { error: "Could not save" };
return { error: null, saved: true };
}
export function NameForm() {
const [state, formAction, isPending] = useActionState(updateName, { error: null });
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>{isPending ? "Savingβ¦" : "Save"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.saved && <p>Saved!</p>}
</form>
);
}No onSubmit, no preventDefault, no separate loading state. React resets uncontrolled form fields after a successful action.
π» Code Example β useFormStatus in a Reusable Button
import { useFormStatus } from "react-dom";
function SubmitButton({ children }) {
const { pending } = useFormStatus(); // reads the nearest parent <form>
return <button disabled={pending}>{pending ? "Please waitβ¦" : children}</button>;
}π» Code Example β useOptimistic for Instant UI
import { useOptimistic, startTransition } from "react";
function LikeButton({ postId, likes }) {
const [optimisticLikes, addOptimistic] = useOptimistic(likes, (current, delta) => current + delta);
function like() {
startTransition(async () => {
addOptimistic(1); // UI updates immediately
await api.like(postId); // if this throws, React reverts to `likes`
});
}
return <button onClick={like}>β€οΈ {optimisticLikes}</button>;
}π» Code Example β use() With Suspense
import { use, Suspense } from "react";
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // suspends until resolved
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
export function Post({ id }) {
// Create the promise outside the suspending component (e.g. in a loader or parent),
// otherwise a new promise is created on every render.
const commentsPromise = useMemo(() => fetchComments(id), [id]);
return (
<Suspense fallback={<p>Loading commentsβ¦</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// use() can also read context, and unlike useContext it may be called inside if/loops
function Panel({ show }) {
if (!show) return null;
const theme = use(ThemeContext);
return <div className={theme}>β¦</div>;
}π» Code Example β ref as a Prop (No More forwardRef)
// React 19
function TextInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// Parent
const inputRef = useRef(null);
<TextInput ref={inputRef} />;
inputRef.current.focus();π» Code Example β Document Metadata
function ProductPage({ product }) {
return (
<>
<title>{`${product.name} | ShopLite`}</title>
<meta name="description" content={product.summary} />
<h1>{product.name}</h1>
</>
);
}
// React hoists <title> and <meta> into <head>.π Server Components (RSC) β The Mental Model
| Server Component (default in Next.js App Router) | Client Component ("use client") |
|
|---|---|---|
| Runs | On the server only (build or request time) | On the server for HTML and in the browser |
| Can use | async/await, DB, file system, secrets |
State, effects, event handlers, browser APIs |
| Ships JS to browser | β No | β Yes |
| Can import | Server and client components | Only client components |
// app/products/page.jsx β Server Component
import { db } from "@/lib/db";
import AddToCart from "./AddToCart"; // a client component
export default async function ProductsPage() {
const products = await db.product.find().lean(); // direct DB access, no API route
return products.map((p) => (
<div key={p._id}>
{p.name} <AddToCart id={String(p._id)} />
</div>
));
}// AddToCart.jsx
"use client";
export default function AddToCart({ id }) {
return <button onClick={() => addToCart(id)}>Add</button>;
}Server Actions ("use server") are functions that run on the server and can be passed to <form action> from either kind of component.
In a classic MERN app (Vite + Express API), you will not use Server Components β they need a framework such as Next.js. Actions,
useOptimistic,useActionState,useandrefas a prop all work in a Vite SPA.
π The React Compiler
The compiler analyses components at build time and inserts memoisation automatically, so most hand-written useMemo, useCallback and React.memo become unnecessary. It requires components to follow the Rules of React (pure render, no mutating props/state). Until your project enables it, manual memoisation still matters.
π Real-World Use
- Login, sign-up and settings forms β
useActionState+useFormStatus. - Like buttons, cart quantity, chat messages β
useOptimistic. - Design-system inputs β
refas a prop instead offorwardRef. - Next.js apps β Server Components for data-heavy pages, client components only where interactivity is needed.
π― Likely Interview Questions
- What is new in React 19?
- What is an Action? How does
useActionStatediffer fromuseState? - How does
useOptimisticroll back on failure? - What can
use()do thatuseContextcannot? - Server Components vs Client Components β what can each do?
- Can you use Server Components in a Vite + Express MERN app?
- What does the React Compiler do? Does it make
useMemoobsolete? - How did
forwardRefchange in React 19?
β Performance Optimization | Index | Next: Concurrent Rendering & Suspense β