React 19 - Actions, Compiler, ref-as-prop, use()
React 19 introduces a smoother way to handle async tasks such as:
On this page
- 1) Actions: standardizing how async work (form submits, API calls) is handled
- 2) useActionState (state derived from an async action)
- 3) useFormStatus (pending/error status for child components inside a )
- 4) useOptimistic (Optimistic UI)
- 5) React Compiler (automatic memoization at build time)
- 6) "Goodbye forwardRef": ref as a regular prop
- 7) use() (an API for reading Promises/Context during render)
- 8) Native metadata: writing , , inside a component
- 9) Quick Summary: React 18 vs React 19
title: React 19 - Actions, Compiler, ref-as-prop, use(), Native Metadata
1) Actions: standardizing how async work (form submits, API calls) is handled
Definition (the core idea)
React 19 introduces a smoother way to handle async tasks such as:
- Submitting a form (server mutations)
- Calling an API to change data
- Stateful asynchronous operations (pending/success/error)
Instead of manually building a bunch of state yourself (isLoading, error, success, …), you use Actions plus the related hooks and let React manage the update flow.
2) useActionState (state derived from an async action)
Role
useActionState helps manage state produced by an asynchronous action, commonly seen with form submissions:
- You get back the current state (e.g., returned data or an error).
- React manages the pending state.
- You get a "form action" to plug into
<form action={...}>.
Syntax (illustrative)
const [state, formAction, isPending] = useActionState(actionFn, initialState);
Example: Submitting a form with useActionState
"use client";
import { useActionState } from "react";
async function loginAction(prevState, formData) {
const email = formData.get("email");
// e.g., calling a server-side API / route handler
const res = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email }),
headers: { "Content-Type": "application/json" },
});
if (!res.ok) return { error: "Login failed" };
return { error: null, message: "Logged in successfully" };
}
export default function LoginForm() {
const [state, formAction, isPending] = useActionState(
loginAction,
{ error: null, message: null }
);
return (
<form action={formAction}>
<input name="email" type="email" placeholder="Email" required />
<button disabled={isPending}>
{isPending ? "Processing..." : "Login"}
</button>
{state?.error && <p style={{ color: "red" }}>{state.error}</p>}
{state?.message && <p>{state.message}</p>}
</form>
);
}
Explanation
actionFnreceivesprevState(the previous state) andformData.- React coordinates calling the action and updating the UI based on the result.
3) useFormStatus (pending/error status for child components inside a <form>)
Role
useFormStatus lets a child component (nested inside a <form> tree) read that form's status:
pending: whether it's currently submittingdata,method,action: provided depending on the environment/renderer
The important point: useFormStatus avoids "prop drilling" for submit state.
Example: a child Button that knows about pending via useFormStatus
"use client";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Submitting..." : "Submit"}
</button>
);
}
export default function CreatePostForm({ formAction }) {
return (
<form action={formAction}>
<input name="title" placeholder="Title" />
<SubmitButton />
</form>
);
}
4) useOptimistic (Optimistic UI)
Definition
useOptimistic lets you show an "optimistic" UI while an asynchronous action is running:
- Immediately shows the expected result (the optimistic state).
- Once the server returns the real result, React reconciles back to real state.
- If the server fails, you roll back to the previous state (per the hook's rollback mechanism).
Role / Use Cases
- A Like/Un-like button
- Adding to a cart
- Creating a comment and showing it instantly before the server confirms it
Example: Liking a post
"use client";
import { useOptimistic } from "react";
async function likeAction(postId) {
const res = await fetch(`/api/posts/${postId}/like`, { method: "POST" });
if (!res.ok) throw new Error("Like failed");
return res.json(); // e.g., { liked: true, likeCount: 123 }
}
export default function LikeButton({ postId, liked, likeCount }) {
const [optimisticState, addOptimistic] = useOptimistic(
{ liked, likeCount },
(currentState, _postId) => ({
...currentState,
liked: !currentState.liked,
likeCount: currentState.liked
? currentState.likeCount - 1
: currentState.likeCount + 1,
})
);
async function onClick() {
// Show it immediately (optimistic)
addOptimistic(postId);
// Run the real action (roll back per the hook's mechanism if it errors)
await likeAction(postId);
}
return (
<button onClick={onClick}>
{optimisticState.liked ? "Liked" : "Like"} ({optimisticState.likeCount})
</button>
);
}
5) React Compiler (automatic memoization at build time)
Definition
React Compiler is a build-time tool (usually a plugin for a build tool/Babel) that analyzes your code and automatically applies memoization optimizations (equivalent to writing useMemo, useCallback, or React.memo in the right places).
The key point: you don't "rewrite" your logic - the compiler optimizes based on static analysis and the Rules of React.
Role / Benefits
- Reduces manual boilerplate around
useMemo/useCallback. - Reduces the risk of memoizing with the wrong dependencies (or forgetting to memoize).
- Targets performance by cutting down unnecessary re-renders.
Practical Limitations
- It optimizes best when your code follows the rules: no mutation/side effects during render, preserving referential expectations, etc.
- You should still profile when there's a real bottleneck.
6) "Goodbye forwardRef": ref as a regular prop
Definition
In React 19, ref has a simpler approach: a function component can receive ref as a normal prop.
So forwardRef becomes far less necessary and is marked deprecated.
Example: replacing forwardRef with the ref prop
Before (React 18):
import { forwardRef } from "react";
const MyInput = forwardRef(function MyInput(props, ref) {
return <input ref={ref} {...props} />;
});
After (React 19):
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
Explanation
- The function component receives
reffrom the parent. - You pass
refdirectly down to the DOM node (or any child component that accepts aref).
7) use() (an API for reading Promises/Context during render)
Definition
use() is a new API in React 19 for reading a value from:
- A Promise: React will "suspend" until the promise resolves (combined with
Suspense). - A Context: similar to
useContext, but more flexible since it can be called conditionally or in a loop.
The important point: use is not a "hook" in the traditional Rules of Hooks sense, so you can call it inside an if/loop (and it still runs during render).
Example: use(promise) combined with Suspense
import { Suspense, use } from "react";
function User({ userPromise }) {
const user = use(userPromise); // pending => suspend
return <div>{user.name}</div>;
}
export default function UserPage({ userPromise }) {
return (
<Suspense fallback={<div>Loading user...</div>}>
<User userPromise={userPromise} />
</Suspense>
);
}
Example: use(context) inside a condition
import { use } from "react";
function ConditionalTheme({ show }) {
if (!show) return null;
const theme = use(ThemeContext);
return <div style={{ color: theme.primary }}>Theme!</div>;
}
Important Notes
- The Promise passed to
use(promise)should stay stable across renders; if a new promise is constantly created, the UI can keep suspending repeatedly. - If the promise rejects, you need a nearby
Error Boundaryto show an error UI. - Don't use
use()arbitrarily inside event handlers; it's meant for the render flow.
8) Native metadata: writing <title>, <meta>, <link> inside a component
Definition
React 19 lets you render metadata tags directly inside the component tree (e.g., title, meta, link), and React will hoist them into <head>.
Role
- Reduces the need for libraries like
react-helmetin many common cases. - Makes metadata work "naturally" with SSR and streaming.
Example
export default function ProductPage({ product }) {
return (
<>
<title>{product.name} | My Store</title>
<meta name="description" content={product.summary} />
<link rel="canonical" href={`https://example.com/products/${product.slug}`} />
<h1>{product.name}</h1>
</>
);
}
Explanation
- React automatically pushes those tags into
<head>. - If multiple pieces of metadata appear together, you need to understand the "which tag wins" rule for your specific framework/renderer's tree.
9) Quick Summary: React 18 vs React 19
-
React 18 focused on Concurrent Rendering:
- Automatic Batching
- Transitions (
useTransition,useDeferredValue) - Suspense + Streaming SSR
-
React 19 focuses on Developer Experience & Async/UI Primitives:
- Actions +
useActionState,useFormStatus useOptimisticfor optimistic UI- React Compiler auto-memoizing at build time
refas a regular prop, reducingforwardRefboilerplateuse()for reading Promises/Context during render- Native metadata hoisted into
<head>
- Actions +
