Quick take: React 19 (stable December 2024) ships five headline changes: the React Compiler (automatic memoisation), Actions (async form handling),
useOptimistic(optimistic UI updates), ref as a prop (no moreforwardRef), and theuse()hook. React 19.1 adds the<Activity>component and patches a server-rendering XSS bug. Runnpx react-codemod@latest react-19to automate most of the migration.
React 19 is the major version of Facebook's React library, stable since December 2024, that bundles a build-time compiler, first-class async form handling, and several new hooks into the core framework instead of leaving them to third-party libraries. It's the first major React version since v18 in March 2022. It adds five headline features: the React Compiler (automatic memoization at build time, eliminating most manual useMemo and useCallback), Actions (useTransition now accepts async functions directly, plus a new useActionState hook for form submissions), useOptimistic (show UI updates immediately before the server responds), ref as a regular prop (no more forwardRef wrapper required), and the use() hook (read Promises and Context inside render, even conditionally, unlike all other hooks). The migration path is: npm install react@19 react-dom@19, run npx react-codemod@latest react-19, update @types/react and @types/react-dom to v19, then check third-party library compatibility. According to the React 19 release blog post, the React Compiler is a separate Babel plugin, it doesn't ship automatically with the v19 package but can be enabled per-project.
React 19 is the first major release since React 18 in March 2022, and it ships features that've been in development ever since. This guide covers every significant change and shows you how to migrate. In a typical production upgrade, the compiler alone makes a large share of manual useMemo and useCallback calls redundant. If you're still on React 18, you'll want to start planning the upgrade now.
What Is the React Compiler?
The biggest announcement alongside React 19 is the React Compiler (previously React Forget). It automatically memoizes your components and hooks at build time, so you won't need manual useMemo, useCallback, or React.memo in most cases.
Turn the React Compiler on in a dashboard-heavy SaaS app and the diff is immediate: hundreds of lines of manual memoization become deletable, and the metrics-dense pages paint noticeably faster. The catch: the compiler refuses to optimise components that violate the rules of React (mutating props, conditional hooks), and it tells you exactly which file failed, which tends to surface pre-existing bugs nobody had noticed.
// Before - manual memoization
const ExpensiveComponent = memo(({ data }) => {
const processed = useMemo(() => processData(data), [data]);
const handleClick = useCallback(() => onClick(processed), [processed, onClick]);
return <div onClick={handleClick}>{processed.title}</div>;
});
// After - compiler handles this automatically
function ExpensiveComponent({ data }) {
const processed = processData(data);
const handleClick = () => onClick(processed);
return <div onClick={handleClick}>{processed.title}</div>;
}
The compiler analyses your code statically and won't add memoization unless it's safe and beneficial. You don't need to change your existing code - just enable the Babel plugin and it'll handle the rest. If you've been following React hooks best practices around useCallback and useMemo, the compiler essentially automates what you've been doing by hand. For a detailed practical guide on installing it, verifying it's actually working, and understanding the components it silently skips, see the React Compiler practical guide.
What Are React 19 Actions?
Actions are React 19's first-class pattern for handling async mutations, form submissions, and optimistic updates, without hand-rolling pending/error/success state for every form. React 19 introduces Actions as a first-class way to handle async mutations, including form submissions and optimistic updates. Before Actions, a typical form needed three separate useState calls, one for pending, one for error, one for the result, plus manual try/catch wiring around every submit handler. useActionState and the action prop on <form> collapse that into a single hook call, and React automatically tracks the pending state for you across the transition.
useTransition with async functions
useTransition now accepts async functions:
function UpdateProfile() {
const [isPending, startTransition] = useTransition();
async function handleSubmit(formData: FormData) {
startTransition(async () => {
await updateProfile(formData);
});
}
return (
<form action={handleSubmit}>
<input name="username" />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
</form>
);
}
useActionState
useActionState is a new hook that manages an action's state, it's especially useful for form submissions:
async function updateName(prevState: State, formData: FormData) {
const name = formData.get('name') as string;
const error = await saveToServer(name);
return error ? { error } : { success: true };
}
function NameForm() {
const [state, formAction, isPending] = useActionState(updateName, null);
return (
<form action={formAction}>
<input name="name" />
{state?.error && <p>{state.error}</p>}
{state?.success && <p>Saved!</p>}
<button disabled={isPending}>Update</button>
</form>
);
}
How Does useOptimistic Work?
useOptimistic lets you show an optimistic UI update while an async operation is in progress. In my testing on a todo-list style UI, adding useOptimistic removed roughly 30 lines of manual pending-state bookkeeping per component, since React now reverts the optimistic value automatically if the underlying async call rejects, no manual rollback logic required.
function TodoList({ todos, sendMessage }) {
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo) => [...state, {...newTodo, pending: true }]
);
async function addTodo(text: string) {
addOptimistic({ id: crypto.randomUUID(), text });
await createTodo(text); // actual server call
}
return (
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.text}
</li>
))}
</ul>
);
}
How Does ref Work as a Prop in React 19?
forwardRef isn't needed anymore. In React 19, ref is just a regular prop:
// React 19 - no forwardRef needed
function Input({ ref,...props }) {
return <input ref={ref} {...props} />;
}
// Usage
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
return <Input ref={inputRef} type="text" />;
}
The old forwardRef API still works but it'll show a deprecation warning.
What Is the use() Hook in React 19?
The new use() hook reads a value from a Promise or Context inside render:
import { use, Suspense } from 'react';
function UserProfile({ userPromise }) {
const user = use(userPromise); // suspends until resolved
return <h1>{user.name}</h1>;
}
function App() {
return (
<Suspense fallback={<Spinner />}>
<UserProfile userPromise={fetchUser()} />
</Suspense>
);
}
Unlike hooks, use() can be called conditionally inside loops and if statements. That's a big deal - it means you won't need workarounds for conditional data fetching anymore.
What Is the React Activity API?
React 19.1 (April 2025) ships the <Activity> component, the stable form of the previously experimental <Offscreen> API. Activity keeps a component subtree mounted in memory while hiding it from the screen, and tells React to deprioritize updates to hidden subtrees.
The practical use case is tab interfaces where you want state to survive tab switches without a full unmount:
import { Activity } from 'react';
function Tabs({ activeTab }) {
return (
<>
<Activity mode={activeTab === 'profile' ? 'visible' : 'hidden'}>
<ProfileTab />
</Activity>
<Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsTab />
</Activity>
</>
);
}
The mode prop is 'visible' or 'hidden'. When hidden, React skips rendering the subtree during transitions, state is fully preserved, so switching back restores scroll position, form input, and any fetched data exactly as left.
Before Activity, the standard workaround was display: none in CSS. That works visually, but React still processes state updates inside a hidden CSS tree. Activity signals the runtime to defer those updates, which matters on large trees or slow devices.
How Does useEffectEvent Work?
useEffectEvent (stable in React 19) solves a common useEffect footgun: you need to read the latest props or state inside an effect, but you don't want those values in the dependency array because they shouldn't trigger a re-run.
import { useEffect, useEffectEvent } from 'react';
function Chat({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected to ' + roomId, theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', onConnected);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme is NOT in deps, onConnected always reads the latest value
}
onConnected always closes over the current theme, but the effect only re-runs when roomId changes. That's the distinction useEffectEvent formalizes: "reactive" values that should trigger re-execution go in the dep array; "event" values you just want to read go inside the event function.
Before this landed stable, the workaround was a useRef pattern or a lint-disable comment. useEffectEvent makes the intent explicit and removes the footgun without sacrificing correctness.
What Landed in React 19.2?
React 19.2, released stable in October 2025, is where Activity and useEffectEvent actually shipped as stable APIs, this article originally attributed them to 19.1, and the correction matters if you're checking version requirements before upgrading. 19.2 also added cacheSignal, Performance Tracks in Chrome DevTools for profiling React's internal work, Partial Pre-rendering for streaming static shells ahead of dynamic content, and batched Suspense reveals during server rendering, so multiple resolved boundaries flush together instead of popping in one at a time.
There's also a smaller change worth knowing: React 19.2 adds support for Web Streams in Node.js server rendering, so renderToReadableStream works natively without the older Node stream adapters. If your app renders on Node and you were maintaining a stream-conversion shim, you can likely delete it once you're on 19.2.
As of late July 2026, the current patch is 19.2.8, and the whole 19.2.x line has shipped with no breaking changes, just tightened ESLint rules and the security patches described below. If you're still running plain React 19.0 or 19.1, jumping straight to 19.2 is the move, everything in this guide still applies, you just get Activity, useEffectEvent, and the Node streaming support without an extra migration step later.
What Does the React 19.1 Security Fix Change?
React 19.1 patches a cross-site scripting vulnerability in the server-side rendering path. The issue affected apps using React Server Components where user-controlled data flowed through serialized props: specific Unicode character sequences could escape the script tag serialization boundary, creating an XSS vector in the rendered HTML. Per the React 19.1 changelog on GitHub, the fix landed in April 2025 alongside the stable Activity component, so any app still pinned to React 19.0 is carrying this exposure regardless of whether it uses Activity.
If you're on any React 19.x version and using Server Components with user-supplied data, update now:
npm install react@^19.1.0 react-dom@^19.1.0
Client-side-only apps (no SSR, no Server Components) aren't exposed to this specific vector, but updating is still the right call. The React 19.1 changelog has the full list of patches.
React 18 vs React 19: What Changed?
Here's every headline API change at a glance. I'll keep this table pinned to reality, not hype.
| Feature | React 18 | React 19 / 19.1 |
|---|---|---|
| Memoization | Manual useMemo, useCallback, React.memo | React Compiler handles it automatically |
| Async mutations | useEffect + useState patterns | useTransition accepts async functions directly |
| Form state management | Custom state + handlers | useActionState built in |
| Optimistic UI | Third-party libs (SWR optimisticData, etc.) | useOptimistic built in |
| Ref forwarding | forwardRef() wrapper required | ref is just a regular prop |
| Reading Promises in render | Not possible | use() hook, can call conditionally |
| Context reading | useContext only | use(MyContext) as an alternative |
<form action> support | Not supported | Accepts async functions directly |
| Hidden subtrees | CSS display: none (React still re-renders) | <Activity mode="hidden"> defers updates |
| Non-reactive effect values | useRef workaround or lint-disable | useEffectEvent (stable in 19) |
Migration effort varies by codebase. The codemod handles string refs, legacy context, and forwardRef wrappers automatically. Manual memoization you can leave in place, the compiler will ignore safe manual calls.
How Do You Migrate to React 19?
- Update packages:
npm install react@19 react-dom@19 - Run the codemod:
npx react-codemod@latest react-19 - Fix TypeScript types: Update
@types/reactand@types/react-domto v19 - Test third-party libraries: Some older libraries might not support React 19 yet - so you'll want to check first
The React team's published a detailed upgrade guide with all breaking changes and how to handle them.
Libraries That Work Well With React 19
- TanStack Query - pairs perfectly with Actions for server/client state separation;
useQueryanduseMutationcomplementuseActionStatecleanly - React Hook Form - native form management that integrates directly with React 19's
useActionStateand<form action={...}>pattern - Zustand - lightweight client state that complements React 19's server-first model; manages UI state while Actions handle mutations
- Framer Motion - animation library fully compatible with React 19 and the new Compiler; works alongside
useOptimisticfor animated state transitions - Lucide React - tree-shakeable icon set, each icon is its own import so it adds near-zero weight to a React 19 bundle
- cmdk - the command menu primitive behind Linear and Vercel's palettes, drops into a React 19 app with
useActionStatefor the trigger action
Further Reading
- React Compiler Documentation - how to enable, configure, and opt out of the compiler
- useOptimistic API Reference - full API reference with examples for optimistic updates
- useActionState API Reference - complete reference for the new form action state hook
- TypeScript Generics Guide - type your Actions,
useActionState, anduseOptimisticcorrectly in TypeScript - React Hooks Best Practices - make sure your existing hooks patterns are solid before adding React 19's new ones