React 19: What's New and How to Use It
React 19 brings Actions, the new use() hook, improved ref handling, and better support for async operations. Here's a practical guide to everything new.
React 19 is here, and it's packed with features that fundamentally improve how we handle forms, async operations, and component composition. Let's dive into what's new and how you can start using these features today.
Actions: Simplified Form Handling
Actions are the biggest addition in React 19. They let you handle form submissions with automatic pending states, error handling, and optimistic updates.
function ContactForm() {
async function submitContact(formData) {
"use server";
const name = formData.get("name");
const email = formData.get("email");
await saveToDatabase({ name, email });
}
return (
<form action={submitContact}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Submit</button>
</form>
);
}useActionState Hook
For more control over form state, use the new useActionState hook:
import { useActionState } from "react";
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (previousState, formData) => {
const email = formData.get("email");
const password = formData.get("password");
try {
await authenticate(email, password);
return { success: true };
} catch (error) {
return { error: error.message };
}
},
{ error: null, success: false }
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<input name="password" type="password" required />
{state.error && <p className="error">{state.error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Signing in..." : "Sign In"}
</button>
</form>
);
}The use() Hook
The use() hook is a new primitive for reading resources during render - including promises and context.
Reading Promises
import { use, Suspense } from "react";
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
function UserProfile({ userPromise }) {
// use() will suspend until the promise resolves
const user = use(userPromise);
return (
<div className="profile">
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
function App({ userId }) {
// Create the promise at the top level
const userPromise = fetchUser(userId);
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}Conditional Context
Unlike useContext, you can call use() conditionally:
function StatusMessage({ showDetails }) {
if (showDetails) {
const theme = use(ThemeContext);
return <DetailedStatus theme={theme} />;
}
return <SimpleStatus />;
}useOptimistic Hook
Show optimistic UI updates while async operations are in progress:
import { useOptimistic } from "react";
function TodoList({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, newTodo) => [...currentTodos, newTodo]
);
async function handleSubmit(formData) {
const title = formData.get("title");
const newTodo = { id: crypto.randomUUID(), title, pending: true };
// Immediately show the new todo
addOptimisticTodo(newTodo);
// Actually save it
await addTodo(title);
}
return (
<>
<form action={handleSubmit}>
<input name="title" placeholder="New todo..." />
<button type="submit">Add</button>
</form>
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.6 : 1 }}>
{todo.title}
</li>
))}
</ul>
</>
);
}Improved Ref Handling
Ref as a Prop
Function components can now accept ref as a regular prop - no more forwardRef:
// Before (React 18)
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
// After (React 19)
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}Ref Cleanup Functions
Refs can now return cleanup functions, similar to useEffect:
function VideoPlayer({ src }) {
return (
<video
ref={(video) => {
if (video) {
video.play();
}
// Cleanup function
return () => {
video?.pause();
};
}}
src={src}
/>
);
}Document Metadata Components
Render metadata directly in your components:
function BlogPost({ post }) {
return (
<article>
<title>{post.title} | My Blog</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={`/blog/${post.slug}`} />
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}React will automatically hoist these to the document head. No more need for Helmet or similar libraries!
Stylesheet Precedence
Control stylesheet loading order with the precedence prop:
function Component() {
return (
<>
<link rel="stylesheet" href="/base.css" precedence="default" />
<link rel="stylesheet" href="/theme.css" precedence="high" />
</>
);
}Async Script Support
Load scripts asynchronously within components:
function Analytics() {
return <script async src="https://analytics.example.com/script.js" />;
}React deduplicates scripts and handles loading automatically.
Migration Tips
Here's how to smoothly transition to React 19:
- Update gradually: Most React 18 code works unchanged
- Replace forwardRef: Convert to the new ref prop pattern
- Adopt Actions: Start with new forms, then migrate existing ones
- Test useOptimistic: Great for improving perceived performance
// Quick migration example for forms
// Before
function OldForm() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
async function handleSubmit(e) {
e.preventDefault();
setIsPending(true);
try {
await submitForm(new FormData(e.target));
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
}
return <form onSubmit={handleSubmit}>...</form>;
}
// After
function NewForm() {
const [state, action, isPending] = useActionState(submitForm, {});
return <form action={action}>...</form>;
}Wrapping Up
React 19 represents a significant step forward in developer experience. Actions simplify form handling, use() makes async data fetching more intuitive, and the improved ref handling removes unnecessary boilerplate.
The best part? These features are designed to work with existing code, so you can adopt them incrementally as you build new features.
What feature are you most excited about? I'm particularly enjoying how Actions have simplified my form components.