What's New in React 19
Actions, the use() API, ref as a prop, document metadata, and the React Compiler — a field guide to the features that change how you actually write React.
React 19 is the largest release in years, and unlike a lot of major versions it isn't mostly internals. The headline features change the day-to-day shape of the code you write: data mutations get a first-class primitive, reading async values inside render becomes legal, and a pile of long-standing boilerplate quietly disappears. Here's the tour, with the bits that actually matter in practice.
Actions and the new form story
For a decade, the "submit a form, show a spinner, handle the error, reset the
field" loop was something you wired up by hand every single time. React 19
introduces Actions — async functions you pass straight to a transition or a
<form> — and a set of hooks built on top of them.
The core hook is useActionState. You give it an async function and an initial
state; it hands back the latest state, a wrapped action to dispatch, and a
pending flag.
function ChangeName({ currentName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"))
if (error) return error
redirect("/profile")
return null
},
null,
)
return (
<form action={submitAction}>
<input type="text" name="name" defaultValue={currentName} />
<button type="submit" disabled={isPending}>Save</button>
{error && <p>{error}</p>}
</form>
)
}
No useState for the pending flag, no try/finally to flip it back, no manual
event handler. The transition is managed for you, so the UI stays responsive
while the async work runs.
Two companions round this out:
useFormStatusreads the pending state of the nearest parent<form>without prop-drilling — handy for a design-system submit button that needs to disable itself.useOptimisticlets you render the expected result immediately and have React reconcile it once the real action resolves. It's the cleanest optimistic UI primitive React has ever shipped.
const [optimisticName, setOptimisticName] = useOptimistic(currentName)
use() — reading promises and context in render
use is a new API that reads the value of a resource. Pass it a promise and it
suspends until the promise resolves; pass it a context and it reads the value —
but unlike useContext, you can call use conditionally, inside loops, and
after early returns.
function Comments({ commentsPromise }) {
// Suspends here until the promise resolves.
const comments = use(commentsPromise)
return comments.map((c) => <p key={c.id}>{c.text}</p>)
}
The pattern that unlocks: a Server Component kicks off the fetch (without
awaiting it), passes the promise down, and a Client Component uses it under a
<Suspense> boundary. The waterfall collapses.
ref as a prop — goodbye forwardRef
forwardRef was one of those APIs everyone learned and nobody liked. In React 19,
ref is just a regular prop on function components.
function TextInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />
}
// <TextInput ref={inputRef} /> — works directly.
forwardRef still works, but it's now deprecated and a codemod can strip it for
you. Ref callbacks also gained a cleanup function: return a function from
your ref callback and React runs it on unmount, mirroring useEffect.
<div ref={(node) => {
const observer = new ResizeObserver(/* … */)
observer.observe(node)
return () => observer.disconnect()
}} />
Document metadata, stylesheets, and scripts
React 19 can hoist <title>, <meta>, and <link> tags rendered anywhere in
your tree up into the document <head> automatically. You can colocate a page's
metadata with the component that owns it:
function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="author" content={post.author} />
<link rel="canonical" href={post.url} />
<h1>{post.title}</h1>
{/* … */}
</article>
)
}
The same machinery understands <link rel="stylesheet" precedence="…"> and
<script async>, deduplicating and ordering them correctly even when multiple
components ask for the same resource. There are also new resource-loading APIs —
preload, preinit, prefetchDNS, and preconnect — for nudging the browser
ahead of time.
Context as a provider
A small but welcome ergonomic change: you can render a context directly as a
provider, dropping the .Provider suffix.
const ThemeContext = createContext("dark")
// Before: <ThemeContext.Provider value="light">
// React 19:
<ThemeContext value="light">
<App />
</ThemeContext>
The React Compiler
Strictly speaking the compiler is a separate project, but it lands in the React
19 era and it's the feature most likely to change how you think. It's a
build-time tool that automatically memoizes components and values — so the manual
useMemo, useCallback, and React.memo dance you do to avoid re-renders
becomes largely unnecessary.
You write straightforward code; the compiler figures out what can be memoized and does it for you. The mental overhead of "should I wrap this in useCallback?" mostly evaporates.
This very site runs with the compiler enabled — reactCompiler: true in the
Next.js config — and the components you'd normally hand-optimize are left plain
on purpose.
Cleaner errors and smaller upgrades
A few quality-of-life wins worth noting: hydration mismatch errors now show a
readable diff instead of a wall of repeated warnings, you can pass an initial
value to useDeferredValue, and custom elements finally have proper support for
properties and attributes.
Should you upgrade?
If you're on React 18, the upgrade path is genuinely smooth — most apps move over
with little more than dependency bumps and the occasional codemod. The payoff is
real: Actions remove a whole category of glue code, use simplifies async data
flow, and the compiler quietly deletes your memoization busywork. React 19 is
less a list of features than a steady reduction in the amount of React you have
to write by hand.