Skip to content

React 19 New Features: Complete Guide and Migration Steps

React 19 guide: Compiler, Actions, useOptimistic, Activity API, and useEffectEvent. Migration steps, code examples, and React 19.1 security fix.

· · 12 min read

Updated: July 29, 2026

React 19 code showing new hooks and Actions in a code editor

Quick Take

React 19 ships the Compiler (auto-memoization), Actions (async form handling), useOptimistic, and the Activity API for background state preservation. The 19.1 patch fixed a XSS vulnerability in hydration, upgrade before deploying any RSC-powered app to production.

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 more forwardRef), and the use() hook. React 19.1 adds the <Activity> component and patches a server-rendering XSS bug. Run npx react-codemod@latest react-19 to 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.

Lines of JavaScript code glowing on a laptop screen in a dark editor
Photo by Behnam Norouzi on Unsplash

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.

A MacBook showing lines of code on a busy developer desk beside a coffee cup
Photo by Christopher Gower on Unsplash

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.

FeatureReact 18React 19 / 19.1
MemoizationManual useMemo, useCallback, React.memoReact Compiler handles it automatically
Async mutationsuseEffect + useState patternsuseTransition accepts async functions directly
Form state managementCustom state + handlersuseActionState built in
Optimistic UIThird-party libs (SWR optimisticData, etc.)useOptimistic built in
Ref forwardingforwardRef() wrapper requiredref is just a regular prop
Reading Promises in renderNot possibleuse() hook, can call conditionally
Context readinguseContext onlyuse(MyContext) as an alternative
<form action> supportNot supportedAccepts async functions directly
Hidden subtreesCSS display: none (React still re-renders)<Activity mode="hidden"> defers updates
Non-reactive effect valuesuseRef workaround or lint-disableuseEffectEvent (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?

  1. Update packages: npm install react@19 react-dom@19
  2. Run the codemod: npx react-codemod@latest react-19
  3. Fix TypeScript types: Update @types/react and @types/react-dom to v19
  4. 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.

A tidy developer workspace with an iMac and a MacBook on a wooden desk
Photo by Domenico Loia on Unsplash

Libraries That Work Well With React 19

  • TanStack Query - pairs perfectly with Actions for server/client state separation; useQuery and useMutation complement useActionState cleanly
  • React Hook Form - native form management that integrates directly with React 19's useActionState and <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 useOptimistic for 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 useActionState for the trigger action

Further Reading

Frequently Asked Questions

Is React 19 backwards compatible?
React 19 includes a codemods for most breaking changes. The majority of apps can upgrade without major rewrites, though some patterns like string refs are fully removed.
When should I start using React 19 in production?
React 19 is stable as of December 2024. If your dependencies support it, you can migrate now - the React team recommends using the latest stable version.
What replaces forwardRef in React 19?
In React 19, ref is now a regular prop, so you can pass it directly without forwardRef. The forwardRef API still works but it's deprecated.
What is the React Compiler and do I need to install it separately?
The React Compiler is a build-time Babel plugin that automatically inserts memoization, replacing most manual useMemo and useCallback calls. It does NOT ship with the react@19 package itself, you install it separately as babel-plugin-react-compiler and enable it in your Babel or Next.js config. It works on React 17+ projects even before upgrading to React 19.
What is the useOptimistic hook in React 19?
useOptimistic lets you show a speculative UI state immediately while an async operation is in flight, then reconcile with the real server response when it arrives. You pass it the current state and an update function; React reverts to the real state if the operation fails. It pairs with Actions to replace the manual loading/error state patterns most apps used before.
What is the React Activity component?
The Activity component (React 19.1, April 2025) keeps a subtree mounted in memory while hiding it visually. Use it in tab interfaces to preserve state, scroll position, fetched data, form input, without unmounting. When hidden, React defers updates to that subtree, reducing CPU work. It's the stable form of the previously experimental Offscreen API.
What is useEffectEvent in React 19?
useEffectEvent extracts non-reactive logic from useEffect. It lets you read the latest props or state inside an effect without including them in the dependency array. You use it when you want an effect to re-run only for certain dependencies but still access other current values, for example, logging analytics without re-subscribing to a connection every time the theme changes.