React 19: Complete Guide to New Features in 2026
Everything new in React 19: use() hook, Actions, optimistic updates, ref as prop, form actions, and the React Compiler. Production-ready patterns with TypeScript examples.
React 19 shipped as stable in late 2024 and by 2026 itβs the foundation of every new React project. The release focused on one theme: making async patterns feel native to React. Server-first data fetching, optimistic mutations, and form handling are now first-class citizens.
This guide covers every meaningful change with production-ready examples β not just descriptions.
The use() Hook
use() is the most versatile addition in React 19. It reads a resource during render β a Promise or a Context β suspending the component until the resource resolves.
Reading a Promise
import { use, Suspense } from 'react';
// Fetch the data OUTSIDE the component
const userPromise = fetch('/api/user').then(r => r.json());
function UserProfile() {
// Suspends until userPromise resolves
const user = use(userPromise);
return <h1>Hello, {user.name}</h1>;
}
function App() {
return (
<Suspense fallback={<div>Loading profile...</div>}>
<UserProfile />
</Suspense>
);
}
Unlike useEffect + useState, use() integrates with Suspense β the component doesnβt render until data is ready. The parentβs fallback shows during the wait.
Critical: create the Promise outside the component. Creating it inside would create a new Promise on every render.
Reading Context Conditionally
The old useContext couldnβt be called conditionally. use() can:
import { use } from 'react';
function Message({ show }: { show: boolean }) {
if (!show) return null; // β early return
// This was impossible with useContext
const theme = use(ThemeContext);
return <p style={{ color: theme.primary }}>Hello</p>;
}
Actions and useTransition
React 19 formalizes the concept of βActionsβ β async functions passed to transitions. React tracks their pending state automatically.
import { useTransition } from 'react';
function UpdateNameForm({ userId }: { userId: string }) {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const name = new FormData(event.currentTarget).get('name') as string;
startTransition(async () => {
const result = await updateUserName(userId, name);
if (result.error) {
setError(result.error);
}
});
}
return (
<form onSubmit={handleSubmit}>
<input name="name" type="text" disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{error && <p className="text-red-500">{error}</p>}
</form>
);
}
isPending is automatically true while the async function inside startTransition is running. No useState needed for loading state.
useActionState
useActionState is purpose-built for managing form state through a Server Action or async function. Itβs the successor to React DOMβs useFormState.
import { useActionState } from 'react';
interface State {
message: string | null;
errors: Record<string, string[]> | null;
}
async function submitContactForm(prevState: State, formData: FormData): Promise<State> {
const email = formData.get('email') as string;
const message = formData.get('message') as string;
if (!email.includes('@')) {
return { message: null, errors: { email: ['Invalid email address'] } };
}
await sendEmail({ email, message });
return { message: 'Message sent!', errors: null };
}
export function ContactForm() {
const [state, formAction, isPending] = useActionState(
submitContactForm,
{ message: null, errors: null }
);
return (
<form action={formAction}>
<input name="email" type="email" required />
{state.errors?.email?.map(e => <p key={e} className="text-red-500">{e}</p>)}
<textarea name="message" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Sending...' : 'Send Message'}
</button>
{state.message && <p className="text-green-500">{state.message}</p>}
</form>
);
}
Three values returned: [state, formAction, isPending].
useOptimistic
Optimistic UI updates have always required complex state management. React 19 solves it with useOptimistic.
import { useOptimistic, useTransition } from 'react';
interface Message { id: string; text: string; sending?: boolean; }
export function MessageThread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(current, newMessage: Message) => [...current, newMessage]
);
const [isPending, startTransition] = useTransition();
async function handleSend(text: string) {
const tempId = crypto.randomUUID();
startTransition(async () => {
// Update UI immediately with "sending" indicator
addOptimisticMessage({ id: tempId, text, sending: true });
// Send to server
await sendMessage(text);
// Server revalidation replaces the optimistic message
});
}
return (
<div>
{optimisticMessages.map(msg => (
<div key={msg.id} className={msg.sending ? 'opacity-60' : ''}>
{msg.text} {msg.sending && <span>Sending...</span>}
</div>
))}
<MessageInput onSend={handleSend} disabled={isPending} />
</div>
);
}
If the server call fails, optimisticMessages automatically reverts to the original messages prop. No manual rollback code.
ref as a Prop
Before React 19, forwarding refs required forwardRef() boilerplate. Now ref works like any other prop.
// Before React 19
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// React 19 β no forwardRef needed
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// Usage
const inputRef = useRef<HTMLInputElement>(null);
<Input ref={inputRef} type="text" />
The old forwardRef API still works for backward compatibility, but new components should use the prop-based approach.
Improved Hydration Error Messages
React 19 dramatically improved hydration mismatch error messages. Instead of:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
You now get a diff showing exactly which element mismatches and the server vs. client values. This alone saves hours of debugging in complex SSR applications.
Document Metadata
React 19 adds native support for rendering <title>, <meta>, and <link> tags directly in components β they automatically hoist to <head>.
function BlogPost({ post }: { post: Post }) {
return (
<article>
<title>{post.title} | DevPlaybook</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={`https://devplaybook.cc/blog/${post.slug}`} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
No need for next/head or react-helmet in new projects. Works with SSR β React renders the metadata server-side, so crawlers see it immediately.
Stylesheet Priority Management
React 19 manages stylesheet loading order via a precedence prop on <link>:
function Component() {
return (
<>
<link rel="stylesheet" href="/base.css" precedence="default" />
<link rel="stylesheet" href="/theme.css" precedence="high" />
<div className="themed-content">...</div>
</>
);
}
React deduplicates stylesheets across the page and ensures loading order matches precedence levels.
Async Script Support
function Analytics() {
return (
<script async src="https://analytics.example.com/script.js" />
);
}
React 19 deduplicates async scripts β if multiple components render the same src, the script loads once regardless. No more useEffect + document.createElement boilerplate.
The React Compiler
Shipped alongside React 19, the React Compiler automatically applies optimizations that you previously needed useMemo, useCallback, and React.memo for.
// Before: manual memoization required
const expensiveValue = useMemo(() => computeExpensive(a, b), [a, b]);
const stableCallback = useCallback(() => handleClick(id), [id]);
const MemoComponent = React.memo(MyComponent);
// After: the compiler handles this automatically
// Write plain React β the compiler adds memoization where needed
const expensiveValue = computeExpensive(a, b);
const stableCallback = () => handleClick(id);
function MyComponent() { ... }
The compiler analyzes your code statically and inserts memoization at the right granularity β finer-grained than manual React.memo wrapping. Enable it in your Next.js config:
// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};
The opt-out escape hatch:
function ComponentWithSideEffects() {
'use no memo'; // β opt this component out of compiler optimization
// ...
}
Migration from React 18
Most codebases migrate without code changes. Breaking changes are minimal:
| Change | Impact | Action |
|---|---|---|
ReactDOM.render removed | Rare | Use createRoot |
useFormState β useActionState | Common | Rename import |
forwardRef deprecated | Optional | Migrate gradually |
React.FC implicit children removed | Common | Add { children: ReactNode } explicitly |
| Strict mode double-effect cleanup | Testing | Update test assertions |
React 19 vs React 18: Performance
React 19 doesnβt change the core scheduling model, but the Compiler typically reduces re-renders by 30β50% in component trees with expensive calculations. The biggest wins come from:
- Automatic memoization of derived values
- Stable function references without
useCallback use()eliminating waterfall data fetching patterns
The shift in React 19 is philosophical: async is first-class, not bolted on. Actions, use(), and useOptimistic let you write async code the way you think about it β without manually managing every loading/error/success state.
For hands-on practice, try the React hooks reference and TypeScript code formatter.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.