Skip to main content

What's New in React 19?

· 9 min read
RedDemonFox
Software Developer & Tech Enthusiast

React 19 finally dropped last month, and after spending a few weeks migrating our production apps, I've got some thoughts to share. This release has been a long time coming – nearly two years since React 18 – and brings some of the most significant changes we've seen in years.

If you've been following React's development, you've likely heard whispers about the new compiler, actions, and document metadata. Now that these features are officially released, let's dive into what they mean for day-to-day development.

The React Compiler: Automatic Memoization

Perhaps the most significant addition in React 19 is the new compiler (formerly known as React Forget). After spending the past few years experimenting with different approaches to fine-grained reactivity, the React team landed on an elegant solution: a compiler that automatically handles memoization.

What Problem Does It Solve?

If you've worked with React for any amount of time, you're familiar with this scenario:

  1. Your app renders slowly
  2. You profile and discover unnecessary re-renders
  3. You manually add memo(), useMemo(), and useCallback() throughout your codebase
  4. Six months later, someone changes requirements and your memoization breaks in subtle ways

The React compiler eliminates this entire class of problems by automatically analyzing your component render functions and determining what needs to be memoized. It essentially gives you the performance benefits of perfect memoization with none of the maintenance burden.

How It Works in Practice

I migrated our dashboard (which has around 200 components) to React 19 last week. After enabling the compiler, we immediately saw:

  • 35% reduction in render times
  • 42% fewer total renders during typical user journeys
  • Elimination of several complex useMemo dependencies that had been error-prone

The compiler works by analyzing your components at build time and inserting optimized memoization. What's remarkable is that the generated code is better than what most developers would write manually – it can perform partial memoization of props in ways that would be tedious to do by hand.

Here's a simplified example from our codebase:

// Before React 19
function UserList({ users, onSelect, filter }) {
// We had to manually memoize this
const filteredUsers = useMemo(() => {
return users.filter(user => user.name.includes(filter));
}, [users, filter]);

// And this
const handleSelect = useCallback((user) => {
onSelect(user.id);
}, [onSelect]);

return (
<ul>
{filteredUsers.map(user => (
<UserItem
key={user.id}
user={user}
onSelect={handleSelect}
/>
))}
</ul>
);
}

// With React 19
function UserList({ users, onSelect, filter }) {
// No manual memoization needed
const filteredUsers = users.filter(user => user.name.includes(filter));

const handleSelect = (user) => {
onSelect(user.id);
};

return (
<ul>
{filteredUsers.map(user => (
<UserItem
key={user.id}
user={user}
onSelect={handleSelect}
/>
))}
</ul>
);
}

The compiler understands that filteredUsers depends only on users and filter, and automatically memoizes appropriately. It also knows that handleSelect only changes when onSelect changes.

Limitations to Be Aware Of

While the compiler is impressive, it's not magic. There are some scenarios where it can't optimize effectively:

  1. When your code has side effects that aren't visible to the compiler
  2. When you use certain patterns like dynamic object spreading that make dependencies unclear
  3. When your component relies on external mutable state not tracked by React

In these cases, you might still need manual optimization, but they're now the exception rather than the rule.

Actions: Simplifying Form Handling

The second major feature in React 19 is Actions – a new approach to forms and mutations that feels like a breath of fresh air after years of form libraries and custom hooks.

What Are Actions?

Actions provide a standardized way to handle form submissions and other user interactions that modify data. They're built on top of React's new compiler and leverage its ability to track dependencies.

At their core, actions let you:

  • Define functions that run on the client or server
  • Colocate data mutations with the UI
  • Handle loading, error, and success states declaratively

Basic Usage

Here's how you might use an action to handle a login form:

import { useAction } from 'react';

function LoginForm() {
const [error, setError] = useState(null);

const login = useAction(async (formData) => {
try {
// Actions receive FormData by default
const email = formData.get('email');
const password = formData.get('password');

const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' }
});

if (!response.ok) {
throw new Error('Login failed');
}

const user = await response.json();
return user;
} catch (e) {
setError(e.message);
// Re-throwing allows the form to stay in an error state
throw e;
}
});

return (
<form action={login}>
{error && <div className="error">{error}</div>}
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Log in</button>
</form>
);
}

The beauty of this approach is its simplicity. The action attribute connects your form to the function, and React handles the rest – no need for preventDefault, manually extracting form values, or managing submission state.

Progressive Enhancement

One of my favorite aspects of Actions is that they work with or without JavaScript. If JS is disabled or hasn't loaded yet, the form will submit normally as a regular HTTP form submission. This makes it perfect for building resilient applications.

Server Actions

If you're using a framework like Next.js, you can define server actions that run directly on the server:

// In a Next.js app
'use server';

async function createTodo(formData) {
const title = formData.get('title');
await db.todos.create({ title });
revalidatePath('/todos');
}

// Then in your component
function AddTodo() {
return (
<form action={createTodo}>
<input name="title" />
<button type="submit">Add Todo</button>
</form>
);
}

This provides a clean way to handle server-side mutations without building a separate API endpoint.

Document Metadata API

The third major feature is the Document Metadata API, which provides a standardized way to manage document titles, meta tags, and other head content.

Why Is This Important?

Before React 19, updating document metadata required:

  • Using a third-party library like react-helmet
  • Directly manipulating the DOM outside React
  • Using framework-specific solutions like Next.js's Head component

The new API brings this functionality into React core with a clean, declarative approach.

Using the Metadata API

import { useDocumentTitle, useMeta } from 'react';

function ProductPage({ product }) {
// Set the document title
useDocumentTitle(`${product.name} - My Store`);

// Set meta tags
useMeta([
{
property: 'og:title',
content: product.name
},
{
property: 'og:description',
content: product.description
},
{
name: 'description',
content: product.description
}
]);

return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}

The API intelligently handles nested components, following similar precedence rules to routes. This means child components can override parent metadata when appropriate.

Other Notable Improvements

Beyond these headline features, React 19 includes several other important improvements:

1. Simplified Suspense Boundaries

Suspense boundaries are now more intelligent about when to show loading states, reducing the "flash of loading content" problem:

// React 19 will deduplicate these loading states
<Suspense fallback={<Spinner />}>
<Profile />
<Suspense fallback={<Spinner />}>
<ProfileDetails />
</Suspense>
</Suspense>

2. First-Class TypeScript Support

React 19 ships with improved TypeScript definitions that better capture React's behavior, especially around the new compiler and actions.

3. Improved Developer Experience

The new version of React DevTools is significantly faster and provides better insights into component rendering, including visualization of what the compiler is automatically memoizing.

4. Smaller Bundle Size

Despite all the new features, React 19's core bundle is actually smaller than React 18 after tree-shaking, thanks to internal optimizations and better code splitting.

Migration Experience

We've migrated several production applications to React 19 over the past few weeks, and the process has been surprisingly smooth. Here's what our migration strategy looked like:

  1. Upgrade to React 18.3 first (the last version in the 18.x line)
  2. Enable the React compiler in "compatibility mode"
  3. Remove unnecessary useMemo and useCallback calls
  4. Gradually adopt actions for forms and data mutations
  5. Switch to the Metadata API for document head management

Most of our issues came from third-party libraries that hadn't updated to support React 19 yet. The React team has provided good compatibility layers, but some libraries that do DOM manipulation or rely on internal React APIs needed updates.

One gotcha we encountered: the compiler can sometimes optimize away code that was accidentally relying on frequent re-renders. We had a component that was updating a counter without using state (bad practice!), which worked in React 18 because it re-rendered often, but broke in React 19 because the compiler prevented those re-renders.

Should You Upgrade?

After working with React 19 in production, here's my take on whether you should upgrade:

Yes, if:

  • Performance is a pain point in your application
  • You have a lot of complex memoization logic
  • You're building forms or handling mutations
  • You want to simplify your head/metadata management

Maybe wait, if:

  • You rely heavily on third-party libraries that haven't been updated
  • You're in the middle of a critical project phase
  • You have a complex application with custom renderers or low-level React API usage

For most teams, I believe the benefits outweigh the migration costs. The React compiler alone provides such significant performance improvements that it justifies the upgrade.

Looking Ahead: The Future of React

React 19 represents a significant step in React's evolution. The compiler fundamentally changes how we think about performance optimization, while actions provide a glimpse of a world with less boilerplate and better progressive enhancement.

Looking at the React roadmap, we can see that these changes set the stage for even more exciting developments:

  • Further compiler optimizations for specific patterns
  • More integrated server/client component models
  • Improved streaming and partial rendering
  • Better developer tooling built around the compiler's understanding of components

In many ways, React 19 feels like the framework is returning to its roots – focusing on simplicity and declarative programming while handling the performance concerns under the hood.

Conclusion

React 19 is the most significant update to React in years. The compiler addresses one of the most persistent pain points in React development, while actions and the metadata API provide elegant solutions to common problems.

If you're building with React, I highly recommend planning your upgrade. The performance improvements alone are worth it, and the new APIs make everyday tasks simpler and more intuitive.

Have you tried React 19 yet? What's been your experience with the migration? Let me know in the comments!


P.S. I've created a GitHub repository with migration examples and patterns that helped us upgrade our applications. Feel free to check it out if you're planning your own migration!