React 19 in Practice: Actions, Server Components and the Compiler
React 19 reached stable in late 2024, and it is arguably the most important React release in years for both concurrency and full-stack capabilities. For teams building websites and SaaS products, React 19 is not just "a few new APIs" — it turns form submission, data mutations, and state updates (high-frequency patterns) from hand-written boilerplate into framework defaults.
React 19 remains a core framework under the frontend building and website building categories on 16IDC. To compare React with other frameworks, see the frontend framework comparison; for performance topics, check Next.js SSR performance optimization.
For most teams with existing React code, the real wins are concentrated: login, signup, and guestbook forms can be simplified with Actions right away; high-frequency interactions like likes, favorites, and comments get better feel with useOptimistic; and documentation-style sites fit Server Components to shrink client script size. Each is covered below, with code you can copy directly.
Actions: Declarative Data Mutations
Previously, submitting a form meant manually managing pending state, errors, and sequential requests. In React 19, async functions passed to the <form action> prop (called "Actions") manage submission state automatically:
- Pending state starts and resets automatically
- Works with
useOptimisticfor optimistic updates - On failure, automatically reverts optimistic updates and delegates to Error Boundaries
- Resets the form after successful submission
useActionState bundles "last result + submit function + pending state" into a single return value. Combined with <form action>, you can build a complete form data flow with very little code. Here is a real example — a guestbook form with a loading state:
import { useActionState } from "react";
async function submitMessage(prev, formData) {
await new Promise((r) => setTimeout(r, 800)); // simulate a request
const name = formData.get("name");
if (!name) return { error: "Please enter a nickname" };
return { ok: true, text: `${name}, your message was submitted` };
}
function Guestbook() {
const [state, action, pending] = useActionState(submitMessage, null);
return (
<form action={action}>
<input name="name" placeholder="Your nickname" />
<button disabled={pending}>{pending ? "Submitting…" : "Submit"}</button>
{state?.error && <p style={{ color: "red" }}>{state.error}</p>}
{state?.ok && <p>{state.text}</p>}
</form>
);
}
useOptimistic: The Official Answer to Optimistic Updates
useOptimistic lets you immediately render the "final state" while the request is in flight, then automatically switch back to the real value when it settles. For comments, carts, and favorites, this removes the need to hand-roll "temporary value plus rollback on failure".
import { useOptimistic, useTransition } from "react";
function LikeButton({ likes, onLike }) {
const [optimisticLikes, addOptimistic] = useOptimistic(likes);
const [, startTransition] = useTransition();
return (
<button
onClick={() => {
addOptimistic(optimisticLikes + 1);
startTransition(() => onLike());
}}
>
♥ {optimisticLikes}
</button>
);
}
The like count increments the instant you click, and the framework rolls it back automatically if the request fails — the user feels almost no latency.
use: Reading Resources During Render
The new use API can read a Promise or Context during render and may be called conditionally (unlike hooks). For example, use(promise) suspends the component until the promise resolves, with an outer <Suspense> providing the loading fallback. The key difference is that use can be called inside if branches, which, combined with concurrent rendering, makes for more flexible loading logic. Note that use still requires the promise to resolve or reject, and it is usually paired with <Suspense>; it solves "waiting for data during render," not side effects that belong in useEffect.
Server Components and Server Actions
React Server Components (RSC) let components render ahead of time on the server or at build time, keeping database access and file reads server-side and shipping only results to the client. Combined with the "use server" directive, Server Actions let client components call server functions directly. This is the foundation of the full-stack React architecture and the core concept behind the Next.js App Router (see the Next.js App Router guide).
Use this rule of thumb to decide:
| Component type | Suggestion | Why |
|---|---|---|
| Read-only, data from database/API | Prefer RSC | less client JS; data fetched server-side |
| Highly interactive, frequent state | Client component | manage mutations with Actions |
| Depends on browser-only APIs | Client component | avoids window errors during SSR |
Other Noteworthy Changes
refcan now be passed as a prop to function components;forwardRefis being deprecated.<Context>can be used directly as a Provider.- Native support for document metadata tags (
<title>,<meta>,<link>) that are hoisted to<head>automatically. - Stylesheets are loaded in order by
precedence. - Better hydration error diffs and error reporting.
- Full support for Custom Elements (see the Web Components development guide).
These changes share one direction: React is folding engineering details developers used to decide for themselves — metadata, style ordering, ref passing — into framework defaults. After upgrading to 19, many custom "utility functions" become deletable, such as hand-rolled document.title management or custom style-injection logic.
React Compiler: Automatic Memoization
Alongside React 19, the React Compiler performs automatic memoization at build time, so developers no longer need to hand-write useMemo or React.memo. The compiler works on React 17+, but pairs best with React 19. After upgrading you can delete a large number of hand-written memos; the code shrinks noticeably and the benefit is most visible on interaction-heavy pages.
Adoption Advice
- Start with low-risk components: try Actions and
useOptimisticin forms and lists. - Keep dependencies updated: use the official codemods for
forwardRefand Context migration. - Adopt Server Components selectively: for pure SPAs, start with client-side capabilities only.
- Combine with the compiler: benefits are most visible on interaction-heavy pages.
- Pair your choices: if you are also doing broader tooling selection, evaluate alongside the frontend toolchain 2026.
In general, move in the order of "client-side capabilities first, full-stack capabilities second," and for each new feature, observe performance and interaction on a real page before rolling it out everywhere.
Source: https://react.dev/blog/2024/12/05/react-19
Reference: React useActionState docs https://react.dev/reference/react/useActionState