Command Palette

Search for a command to run...

Hectal
PHASE 19Advanced React 19+ ~23 min· topic 1 of 8

Topic 19.1

What's New in React 19

In one line

React 19 (December 2024) added Actions and the form hooks, use, document metadata, resource loading, ref as a prop and <Context> as a provider, and removed long-deprecated APIs such as ReactDOM.render, string refs, legacy context, propTypes checks and defaultProps on function components; 19.1 and 19.2 added Owner Stacks, <Activity>, useEffectEvent and more.

Think of it like this

A new model of a car. The steering wheel and pedals are where they always were, so you can drive it straight away. But there are new buttons (cruise control, a parking camera), and the old cassette player is gone. Before you drive, you check two lists: what's new, and what was removed so your old cassettes don't surprise you. React 19 is that new model: your components, props, state and hooks work the same, there are new features, and some old parts were taken out.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Major version
A release whose first number changes, like 18 to 19. Only these may break old code.
Action
A function (often async) that React runs inside a transition, tracking its pending state, errors and order.
Transition
An update React treats as not urgent, so typing and clicking stay responsive while it runs.
Deprecated
Still works for now but will be removed later. Usually it prints a warning in development.
Removed
Gone. The old code no longer works or is silently ignored.
Codemod
A program that rewrites your code automatically, for example to move from an old API to a new one.
Legacy context
React's original context API for classes (contextTypes, getChildContext). Replaced by createContext and removed in 19.
String ref
The oldest ref style: ref="box" with this.refs.box. Removed in 19.
Owner Stack
The chain of components that rendered a component. Shown in React 19.1+ error messages in development.

Step by step

01Read the version you actually run

Every package has a version in package.json, and React exports it as version. Check it before you trust a tutorial: an article written for React 17 may use APIs that no longer exist.

terminalwhole filebash
npm ls react react-dom
# my-app@1.0.0
# ├── react@19.2.8
# └── react-dom@19.2.8

02Step 1: go to 18.3 and read the warnings

React 18.3 behaves exactly like 18.2 but warns about everything 19 removes: ReactDOM.render, string refs, defaultProps on functions, legacy context and more. Fix each warning while you are still on a version where the old code works.

terminalwhole filebash
npm install react@18.3 react-dom@18.3
npm run dev   # open the console, fix every warning

03Step 2: install 19 and run the codemods

Install React 19 and its types, then let the codemods do the mechanical edits. Review the diff they make like any other code change: commit before running them so you can see exactly what changed.

terminalwhole filebash
npm install react@^19 react-dom@^19
npm install -D @types/react@^19 @types/react-dom@^19
npx codemod@latest react/19/migration-recipe
npx types-react-codemod@latest preset-19 ./src

04Step 3: replace the removed entry points

The most common change in older apps is the mount line. ReactDOM.render(<App />, el) becomes createRoot(el).render(<App />), and unmountComponentAtNode(el) becomes root.unmount(). createRoot has existed since React 18, so this change works on 18.3 too.

main.jsxwhole filejsx
// Before (React 17 style, removed in 19)
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));

// After
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
root.render(<App />);
// later: root.unmount();

05Step 4: replace the silent removals

Some removals don't throw, they just stop working: defaultProps on function components and propTypes. Search for them (grep -rn "defaultProps\|propTypes" src) and replace them with default parameters and TypeScript types.

Hello.tsxwhole filetsx
// Before: ignored in React 19
function Hello({ name }) { return <p>Hello, {name}</p>; }
Hello.defaultProps = { name: "friend" };
Hello.propTypes = { name: PropTypes.string };

// After: plain JavaScript defaults + TypeScript
function Hello({ name = "friend" }: { name?: string }) {
  return <p>Hello, {name}</p>;
}

06The map of this phase

The rest of the phase follows the new features in the order they build on each other: async transitions first, then forms on top of them, then the hooks that read their state, then the smaller improvements.

The map of this phasediagram
Rendering diagram…

Try it yourself

  1. 1

    Bring the old greeting back

    In the first example, predict what the old greeting shows if you write <OldHello name="Ravi" />. Then change OldHello itself so it shows friend without being passed a name, using a default parameter.

  2. 2

    Find the new exports

    In the second example add a line for typeof React.useEffectEvent and typeof React.Activity. Predict the result (both are React 19.2), then run.

  3. 3

    Unmount with the new API

    Add setTimeout(() => root.unmount(), 500) at the end of the second example. The paragraph disappears: root.unmount() is the replacement for unmountComponentAtNode.

Code & diagrams

Default parameters replace defaultProps on function components React 19+ New tab

`defaultProps` was ignored, so `name` is `undefined`, and the broken `propTypes` validator never ran: no warning at all. Classes still support `static defaultProps`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Old: Hello, undefined
New: Hello, friend
What react-dom exports now React 19+ New tab

The old mount and lookup functions are simply not there any more. Everything new in this phase is a named export from `react` or `react-dom`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

React 19.2.8
render: undefined
hydrate: undefined
unmountComponentAtNode: undefined
findDOMNode: undefined
act in react: function
useActionState: function
useOptimistic: function
use: function
useFormStatus: function
requestFormReset: function
preload: function
The upgrade commands, in orderbash
# 1. Upgrade to 18.3 first and fix every warning in the console
npm install react@18.3 react-dom@18.3

# 2. Install React 19 and its types
npm install react@^19 react-dom@^19
npm install -D @types/react@^19 @types/react-dom@^19

# 3. Rewrite removed JavaScript APIs (ReactDOM.render, string refs,
#    act imports, useFormState, propTypes...). It asks which folder to change.
npx codemod@latest react/19/migration-recipe

# 4. Fix TypeScript type changes (useRef argument, ref callbacks, JSX namespace)
npx types-react-codemod@latest preset-19 ./src

# 5. Review what changed, then test
git diff --stat
npm test

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a string ref in a class component

In a class component, render <div ref="box"> and read this.refs.box in componentDidMount.

terminal
$ Console
── what you'll see ──
Uncaught Error: Expected ref to be a function, an object returned by React.createRef(), or undefined/null.
An error occurred in the <div> component.

Break #2

Rely on defaultProps after upgrading

Keep Hello.defaultProps = { name: "friend" } on a function component and render <Hello />.

terminal
$ Preview
── what you'll see ──
Hello, undefined

Myth vs fact

Myth

React 19 is a rewrite and my components must change.

Fact

Function components, props, state, effects and hooks work as before. Most apps change only the mount line, a few legacy patterns, and types.

Myth

forwardRef was removed in React 19.

Fact

It still works. It is simply no longer needed for function components, because ref is a normal prop. React plans to deprecate it later.

Myth

Actions require a server framework.

Fact

Actions are plain client functions passed to transitions or forms. Server Functions (Phase 20) are one kind of function you *can* pass, not a requirement.

Myth

Removing propTypes makes React less safe.

Fact

propTypes only warned at runtime in development. TypeScript (Phase 12) catches the same mistakes before the code runs, in every environment.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    React 18.3 exists only as an upgrade aid: it is 18.2 plus deprecation warnings for each 19 removal. Upgrading 18.2 → 18.3 → 19 turns silent breakages (like ignored defaultProps) into visible warnings.

  • ▸

    In 19, errors are no longer re-thrown after a boundary catches them, which removes duplicate logs. Error reporting services hook in with createRoot(el, { onCaughtError, onUncaughtError, onRecoverableError }) instead of patching console.error (Topic 19.8).

  • ▸

    useFormState from react-dom was the canary name of useActionState. react-dom 19 still exports it, but it logs ReactDOM.useFormState has been renamed to React.useActionState. Code written against canaries or early Next.js 14 docs needs the rename (Topic 19.4).

  • ▸

    The TypeScript types changed with 19: useRef needs an argument, ref callbacks must not implicitly return a value (cleanup functions are now meaningful), the global JSX namespace moved to React.JSX, and ReactElement['props'] defaults to unknown. types-react-codemod fixes most of these.

Remember this

  1. 1

    A major version is a release whose first number changes (18 → 19). By the rules of semantic versioning (the numbering scheme most npm packages follow), only a major version may break code that worked before. React 19.0 shipped on 5 December 2024. Before it, React 18.3 was released: it is 18.2 plus warnings for everything 19 removes, so the safe path is to upgrade to 18.3 first, fix every warning, then move to 19.

  2. 2

    The big new idea: Actions. An Action is a function, often async, that React runs inside a transition (a non-urgent update, Topic 16.7). React tracks it, gives you a pending flag while it runs, sends thrown errors to the nearest error boundary, and runs updates in order. On top of that sit new APIs: <form action={fn}> and formAction on buttons (Topic 19.3), useActionState (19.4), useFormStatus from react-dom (19.5), useOptimistic (19.6) and requestFormReset (19.3).

  3. 3

    Other new features: use() reads a promise or a context (Topic 14.4). Components can render <title>, <meta> and <link> and React moves them into <head> (document metadata, Topic 19.7). Stylesheets get a precedence prop, async scripts are de-duplicated, and react-dom exports preload, preinit, preconnect and prefetchDNS (19.7). Function components accept **ref as a normal prop**, so forwardRef is no longer needed; ref callbacks can return a cleanup function; and <ThemeContext value={...}> works as a provider without .Provider (Topic 19.8). Server Components and Server Functions became stable for frameworks (Phase 20).

  4. 4

    Removed in React 19 (each one warned in 18.3): ReactDOM.render, ReactDOM.hydrate, unmountComponentAtNode and findDOMNode are gone from react-dom (use createRoot, hydrateRoot, root.unmount() and refs). String refs (ref="box" with this.refs.box) now throw. Legacy context (contextTypes, childContextTypes, getChildContext in class components) was removed: React logs that it was removed and the context is not passed. **propTypes checks** were removed: React no longer reads propTypes, so wrong prop types produce no warning (use TypeScript, Phase 12). **defaultProps on function components** is ignored: use JavaScript default parameters (function Hello({ name = "friend" })). Classes keep static defaultProps. Also removed: createFactory, module pattern factories, the UMD builds (load React from an ESM CDN instead) and the old JSX transform (React 19 requires the new JSX transform from 2020, which every current tool already uses).

  5. 5

    Deprecated or moved: act moved from react-dom/test-utils to react (import { act } from "react"); the old import still works but logs a deprecation warning, and the rest of react-dom/test-utils was removed. react-test-renderer is deprecated: React recommends Testing Library (Phase 17). Reading element.ref on a JSX element now warns (ref is a regular prop). forwardRef still works and will be deprecated in a future version. Errors changed too: in 19, an error caught by a boundary is reported once with console.error instead of being re-thrown, and createRoot accepts onUncaughtError and onCaughtError (Topic 19.8).

  6. 6

    Upgrading a real app: (1) move to React 18.3 and fix its warnings, (2) install react@^19 and react-dom@^19 (and @types/react@^19, @types/react-dom@^19 for TypeScript), (3) run the official codemods (programs that rewrite your source code automatically): npx codemod@latest react/19/migration-recipe for JavaScript changes (string refs, ReactDOM.render, act, propTypes…) and npx types-react-codemod@latest preset-19 ./src for type changes (for example useRef() now needs an argument, and ReactElement props default to unknown). (4) Run your tests and click through the app with the console open.

  7. 7

    React 19.1 (March 2025) added Owner Stacks: captureOwnerStack() from react returns, in development only, the chain of components that *rendered* the current one, which makes error messages point to the right component. React 19.2 (October 2025) added <Activity> to hide parts of the UI while keeping their state (Topic 16.10), useEffectEvent (Topic 7.9), cacheSignal for Server Components, Performance tracks in Chrome DevTools, and partial pre-rendering on the server. All of it is additive: code written for 19.0 runs unchanged.

Explain it without notes

01

What are the headline features of React 19?

02

Which APIs were removed in React 19, and what replaces each?

03

How would you upgrade a large React 18 app to React 19 safely?

04

Why can removing defaultProps support cause bugs that tests miss?

Practice

01

Rewrite this mount code for React 19: ReactDOM.render(<App />, document.getElementById("root")); and later ReactDOM.unmountComponentAtNode(document.getElementById("root"));.

02

Rewrite function Badge({ color, label }) { … } with Badge.defaultProps = { color: "gray", label: "New" } for React 19.

Trade-offs

  • ↔

    Upgrading early gives you Actions and the new hooks, but old third-party libraries that use removed APIs (string refs, findDOMNode, legacy context) may break and need updates or replacements.

  • ↔

    Codemods save hours on large codebases but make mechanical edits; review the diff, because a codemod can't know which defaultProps were relied on.

Done when you can

  • I can list the main new features of React 19.

  • I can name the removed APIs and their replacements.

  • I can describe a safe upgrade path through React 18.3.

  • I can replace defaultProps with default parameters and string refs with useRef.

  • I know what 19.1 and 19.2 added.