B·04 / ENGINEERING
React 19 Features That Actually Change How You Write Code
A practical tour of React 19's biggest features — useActionState, the use API, useOptimistic, ref as a prop, document metadata, and better error reporting.
SEP 07, 2026 · 3 MIN READ

EACT · REACT19 · JAVASCRIPT · FRONTEND
React 19 has quietly reshaped how we write everyday UI code. A lot of patterns that used to need extra libraries or boilerplate are now just... part of React. Here's a practical tour of the features that actually change how you write code day to day.
1. Actions and the useActionState hook
Before React 19, handling a form submission meant wiring up useState for pending/error states, calling preventDefault, and manually managing the async flow. useActionState collapses all of that into one hook.
JAVASCRIPT1import { useActionState } from "react";
2
3async function updateName(prevState, formData) {
4 const name = formData.get("name");
5 if (!name) {
6 return { error: "Name is required" };
7 }
8 await saveName(name);
9 return { error: null };
10}
11
12function NameForm() {
13 const [state, formAction, isPending] = useActionState(updateName, { error: null });
14
15 return (
16 <form action={formAction}>
17 <input type="text" name="name" />
18 <button type="submit" disabled={isPending}>
19 {isPending ? "Saving..." : "Save"}
20 </button>
21 {state.error && <p>{state.error}</p>}
22 </form>
23 );
24}
No more manual useState juggling for pending states. React tracks it for you, and the form still works even before hydration finishes.
2. The use API
use lets you read a promise or context value directly inside render, including conditionally — something hooks never allowed before.
JAVASCRIPT1import { use, Suspense } from "react";
2
3function Comments({ commentsPromise }) {
4 const comments = use(commentsPromise);
5 return (
6 <ul>
7 {comments.map((c) => (
8 <li key={c.id}>{c.text}</li>
9 ))}
10 </ul>
11 );
12}
13
14function Page({ commentsPromise }) {
15 return (
16 <Suspense fallback={<p>Loading comments...</p>}>
17 <Comments commentsPromise={commentsPromise} />
18 </Suspense>
19 );
20}
The component just suspends until the promise resolves. No useEffect, no manual loading state.
3. useOptimistic for instant UI feedback
Optimistic updates used to mean hand-rolling a temporary state and rolling it back on failure. useOptimistic makes this a one-liner pattern.
JAVASCRIPT1import { useOptimistic } from "react";
2
3function TodoList({ todos, addTodo }) {
4 const [optimisticTodos, addOptimisticTodo] = useOptimistic(
5 todos,
6 (state, newTodo) => [...state, { ...newTodo, sending: true }]
7 );
8
9 async function formAction(formData) {
10 const text = formData.get("text");
11 addOptimisticTodo({ id: crypto.randomUUID(), text });
12 await addTodo(text);
13 }
14
15 return (
16 <form action={formAction}>
17 <ul>
18 {optimisticTodos.map((todo) => (
19 <li key={todo.id} style={{ opacity: todo.sending ? 0.5 : 1 }}>
20 {todo.text}
21 </li>
22 ))}
23 </ul>
24 <input type="text" name="text" />
25 <button type="submit">Add</button>
26 </form>
27 );
28}
The UI updates instantly, then reconciles with the real server response once it arrives.
4. No more forwardRef
ref is now just a regular prop on function components. The wrapper that everyone loved to forget is gone.
JSX1function Input({ label, ref }) {
2 return (
3 <label>
4 {label}
5 <input ref={ref} />
6 </label>
7 );
8}
9
10// usage
11const inputRef = useRef(null);
12<Input label="Email" ref={inputRef} />;
5. Document metadata as JSX
You can now render <title>, <meta>, and <link> tags directly from any component, even deep in the tree, and React hoists them into <head> automatically.
JSX1function BlogPost({ post }) {
2 return (
3 <article>
4 <title>{post.title} — My Blog</title>
5 <meta name="description" content={post.description} />
6 <h1>{post.title}</h1>
7 <p>{post.body}</p>
8 </article>
9 );
10}
No more react-helmet for simple cases — it's built in.
6. Better error reporting
React 19 no longer double-logs errors caught by error boundaries, and adds onCaughtError / onUncaughtError root options for centralized error reporting instead of console-only visibility.
JAVASCRIPT1import { createRoot } from "react-dom/client";
2
3const root = createRoot(document.getElementById("root"), {
4 onCaughtError: (error, errorInfo) => {
5 logErrorToService(error, errorInfo.componentStack);
6 },
7});
Wrapping up
React 19's theme is clear: less boilerplate for the async, form-heavy, optimistic-UI patterns that make up most real apps. useActionState, use, and useOptimistic alone remove a huge amount of custom state-management code that used to live in every project. If you've been holding off on upgrading, these are the features that make it worth doing sooner rather than later.