React 19 shipped in late 2024 with significant improvements to async handling, data fetching, and developer experience. The React Compiler, reaching stable in 2025, automates performance optimizations that developers previously handled manually. Together, they simplify React development while improving performance.
React 19 overview
React 19 focuses on making async operations and data handling simpler. The major additions include Actions, the use() hook, and various DX improvements.
| Feature | Description |
|---|---|
| Actions | Async functions for transitions with built-in pending state |
| use() hook | Read promises and context conditionally |
| ref as prop | No more forwardRef for passing refs |
| Improved hydration | Better error recovery and partial hydration |
| Document metadata | Native support for title, meta, link tags |
Actions and useTransition
Actions are async functions wrapped in transitions. React tracks pending state automatically:
'use client';
import { useTransition } from 'react';
export function UpdateButton({ userId }: { userId: string }) {
const [isPending, startTransition] = useTransition();
async function handleUpdate() {
startTransition(async () => {
await updateUser(userId);
// React handles pending state
});
}
return (
<button onClick={handleUpdate} disabled={isPending}>
{isPending ? 'Updating...' : 'Update'}
</button>
);
}
No manual useState for loading. React manages the pending state through the transition.
The use() hook
use() reads a promise or context inside render:
import { use, Suspense } from 'react';
async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
export default function Page({ params }: { params: { id: string } }) {
const userPromise = fetchUser(params.id);
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
Key differences from other hooks:
- Can be called conditionally
- Works with promises and context
- Must be inside Suspense for promises
ref as a regular prop
No more forwardRef wrapper:
// React 18 - required forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return <input ref={ref} {...props} />;
});
// React 19 - ref is a regular prop
function Input({ ref, ...props }: InputProps & { ref?: Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
Simplifies component APIs and reduces boilerplate.
Document metadata
React 19 handles document metadata natively:
function BlogPost({ post }: { post: Post }) {
return (
<>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={`/blog/${post.slug}`} />
<article>{post.content}</article>
</>
);
}
React hoists these elements to <head> automatically. This works alongside Next.js metadata or as a simpler alternative.
The React Compiler
The React Compiler analyzes your code at build time and adds memoization automatically.
How it works
Without the compiler, you manually optimize:
// Manual memoization (pre-compiler)
const MemoizedChild = React.memo(Child);
function Parent({ items }) {
const sortedItems = useMemo(() => items.sort(), [items]);
const handleClick = useCallback(() => console.log(items), [items]);
return <MemoizedChild items={sortedItems} onClick={handleClick} />;
}
With the compiler:
// Compiler handles memoization
function Parent({ items }) {
const sortedItems = items.sort();
const handleClick = () => console.log(items);
return <Child items={sortedItems} onClick={handleClick} />;
}
The compiler analyzes dependencies and adds memoization where beneficial.
What the compiler optimizes
| Optimization | Manual approach | With compiler |
|---|---|---|
| Computed values | useMemo |
Automatic |
| Callback references | useCallback |
Automatic |
| Component re-renders | React.memo |
Automatic |
| Expensive calculations | useMemo |
Automatic |
Installing the compiler
For Babel-based builds:
npm install babel-plugin-react-compiler
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
// Options
}],
],
};
For Next.js 15+:
// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};
ESLint plugin
Add the ESLint plugin to catch patterns the compiler can't optimize:
npm install eslint-plugin-react-compiler
// eslint.config.js
import reactCompiler from 'eslint-plugin-react-compiler';
export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
];
Compiler limitations
The compiler can't optimize everything:
| Pattern | Issue |
|---|---|
| Non-idempotent functions | Side effects in render |
| Mutable data | Modifying objects/arrays in place |
| Dynamic hooks | Hooks called conditionally (except use()) |
| External mutations | Mutating data outside React's control |
The ESLint plugin warns about these patterns.
Migration guide
Step 1: Upgrade to React 19
npm install react@19 react-dom@19
Update type definitions:
npm install -D @types/react@19 @types/react-dom@19
Step 2: Fix breaking changes
Most code works unchanged. Watch for:
- Remove deprecated
propTypesanddefaultPropson function components - Update
forwardRefusage (optional but recommended) - Check custom hooks that rely on render timing
Step 3: Enable the compiler
Start with the ESLint plugin to identify issues before enabling the compiler:
// Enable ESLint plugin first
rules: {
'react-compiler/react-compiler': 'warn',
}
Fix warnings, then enable the compiler in your build.
Step 4: Remove manual memoization
Gradually remove useMemo, useCallback, and React.memo. The compiler handles them. Keep manual memoization only where the compiler warns or for specific edge cases.
Using with React 18
The compiler works with React 17 and 18:
npm install react-compiler-runtime
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-react-compiler', {
runtimeModule: 'react-compiler-runtime',
}],
],
};
Performance impact
In benchmarks, the compiler provides:
| Metric | Improvement |
|---|---|
| Re-render reduction | 20-40% fewer renders |
| Bundle size | Similar (compiler adds small runtime) |
| Build time | Slightly longer (compiler analysis) |
| Runtime performance | Matches or exceeds manual optimization |
The compiler is conservative—it only memoizes when provably safe.
Summary
React 19 simplifies async handling with Actions, adds use() for reading promises, and removes forwardRef boilerplate. The React Compiler automates memoization at build time, letting you write simpler code without sacrificing performance. Upgrade to React 19 for new features, add the compiler for automatic optimization, and gradually remove manual useMemo/useCallback calls.
