Mastering React 19: A Developer’s Guide to New Features and Upgrading from React 18

6 tháng 7, 2025

Introduction

React 19, released on December 5, 2024, brings a host of new features that enhance performance, simplify development, and improve server-side rendering. For developers using React 18, upgrading to React 19 unlocks powerful tools like Server Components, Actions, and new hooks, but it requires careful planning due to breaking changes. This article explores what’s new in React 19, outlines the upgrade process from React 18, and provides practical examples for each key feature to help you get started.

What’s New in React 19

React 19 introduces several transformative features designed to streamline development and optimize performance. Below are the highlights:

  • Server Components: Now stable, these allow rendering on the server to reduce client-side JavaScript, improving load times and SEO. Directives like "use client" and "use server" define where code runs.

  • Actions for Form Handling: Simplify asynchronous form submissions with automatic state management, reducing boilerplate code.

  • New Hooks: Includes useActionState for form state management and useOptimistic for instant UI updates during async operations.

  • Document Metadata Support: Native support for <title> and <meta> tags, eliminating the need for libraries like React Helmet.

  • Improved Suspense and SSR: Faster fallback rendering for suspended components, enhancing user experience in server-rendered apps.

  • Web Components Support: Seamless integration with custom elements, increasing flexibility.

  • React Compiler (Experimental): Automatically optimizes code, reducing the need for manual memoization with useMemo or useCallback.

  • JSX and Ref Improvements: Allows ref as a prop, deprecating forwardRef, and improves JSX performance.

  • Breaking Changes: Deprecated APIs like React.memo, useMemo, forwardRef, and string refs require updates for compatibility.

These features make React 19 a significant step forward, particularly for large-scale applications and those prioritizing performance.

How to Upgrade from React 18 to React 19

Upgrading to React 19 is straightforward but requires attention to breaking changes. Follow these steps to ensure a smooth transition:

  1. Upgrade to React 18.3 First
    React 18.3 includes warnings for APIs deprecated in React 19. Install it to identify potential issues:

    npm install --save-exact react@18.3.0 react-dom@18.3.0
  2. Verify JSX Transform
    Ensure your project uses the modern JSX transform (introduced in React 17). Update your build tool if needed. For Vite:

    // vite.config.js
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    
    export default defineConfig({
      plugins: [react()],
    });
  3. Install React 19
    Update to React 19 with exact versions:

    npm install --save-exact react@^19.0.0 react-dom@^19.0.0
  4. Update TypeScript Types (if applicable)
    For TypeScript projects, update types:

    npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
  5. Run Codemods
    Use codemods to automate migration:

    npx codemod@latest react/19/migration-recipe

    For specific changes (e.g., string refs):

    npx codemod@latest react/19/replace-string-ref
  6. Handle Breaking Changes

    • Replace ReactDOM.render with createRoot and hydrate with hydrateRoot.

    • Migrate string refs to useRef or callback refs.

    • Update Legacy Context in class components to contextType.

    • Check for deprecated APIs like forwardRef or React.memo.

  7. Test Thoroughly
    Test in a staging environment and use React DevTools to monitor performance. Report issues at:
    https://github.com/facebook/react/issues

  8. Adopt New Features Gradually
    Start with non-critical components to experiment with Actions, Server Components, or new hooks.

Note: Some libraries (e.g., Shadcn, Recharts) may not yet fully support React 19. Check compatibility before upgrading.

Examples for Key React 19 Features

Below are practical examples demonstrating how to use React 19’s new features, with comparisons to React 18 where relevant.

1. Actions with useActionState

React 18 (Manual Form Handling):
Manually managing form state with useTransition is verbose:

import { useState, useTransition } from 'react';

function NameForm() {
  const [name, setName] = useState('');
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    startTransition(async () => {
      try {
        const response = await fetch('/api/update-name', {
          method: 'POST',
          body: JSON.stringify({ name }),
        });
        if (!response.ok) throw new Error('Update failed');
      } catch (err) {
        setError(err.message);
      }
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter name"
      />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Submitting...' : 'Submit'}
      </button>
      {error && <p>Error: {error}</p>}
    </form>
  );
}

React 19 (Using Actions):
useActionState simplifies form handling:

import { useActionState } from 'react';

async function updateName(formData) {
  const name = formData.get('name');
  const response = await fetch('/api/update-name', {
    method: 'POST',
    body: JSON.stringify({ name }),
  });
  if (!response.ok) throw new Error('Update failed');
  return { success: true };
}

function NameForm() {
  const [state, action, isPending] = useActionState(updateName, null);

  return (
    <form action={action}>
      <input type="text" name="name" placeholder="Enter name" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Submitting...' : 'Submit'}
      </button>
      {state?.error && <p>Error: {state.error}</p>}
    </form>
  );
}

Why It’s Better: Actions and useActionState reduce boilerplate by handling pending states and errors automatically, improving developer productivity.

2. Server Components

React 18 (Client-Side Only):
Data fetching happens client-side, increasing bundle size:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/api/users')
      .then((res) => res.json())
      .then((data) => setUsers(data));
  }, []);

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

React 19 (Server Component):
Fetches data on the server, reducing client-side JavaScript:

// user-list.server.jsx
async function UserList() {
  const response = await fetch('https://api.example.com/users');
  const users = await response.json();

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

React 19 (Client Component for Interactivity):

// user-list.client.jsx
'use client';

import { useState } from 'react';
import UserList from './user-list.server';

function UserListClient() {
  const [filter, setFilter] = useState('');

  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter users"
      />
      <UserList />
    </div>
  );
}

export default UserListClient;

Why It’s Better: Server Components reduce client-side code, improving load times and SEO, while Client Components handle interactivity.

3. Document Metadata

React 18 (Using React Helmet):
Requires external libraries for SEO:

import { Helmet } from 'react-helmet';

function HomePage() {
  return (
    <div>
      <Helmet>
        <title>Home Page</title>
        <meta name="description" content="Welcome to our site" />
      </Helmet>
      <h1>Welcome</h1>
    </div>
  );
}

React 19 (Native Metadata):
Directly supports metadata tags:

function HomePage() {
  return (
    <div>
      <title>Home Page</title>
      <meta name="description" content="Welcome to our site" />
      <h1>Welcome</h1>
    </div>
  );
}

Why It’s Better: Native support simplifies SEO without additional dependencies.

4. useOptimistic Hook

React 18 (Manual Optimistic Updates):
Requires manual state management:

import { useState, useTransition } from 'react';

function NameUpdater() {
  const [name, setName] = useState('');
  const [optimisticName, setOptimisticName] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setOptimisticName(name);
    startTransition(async () => {
      try {
        await fetch('/api/update-name', {
          method: 'POST',
          body: JSON.stringify({ name }),
        });
        setName(name);
      } catch {
        setOptimisticName('');
      }
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter name"
      />
      <p>Current Name: {optimisticName || name}</p>
      <button type="submit" disabled={isPending}>
        Update
      </button>
    </form>
  );
}

React 19 (Using useOptimistic):
Simplifies optimistic updates:

import { useState, useOptimistic } from 'react';

function NameUpdater() {
  const [name, setName] = useState('');
  const [optimisticName, setOptimisticName] = useOptimistic(name);

  const handleSubmit = async (formData) => {
    const newName = formData.get('name');
    setOptimisticName(newName);
    try {
      await fetch('/api/update-name', {
        method: 'POST',
        body: JSON.stringify({ name: newName }),
      });
      setName(newName);
    } catch {
      setOptimisticName(name);
    }
  };

  return (
    <form action={handleSubmit}>
      <input
        type="text"
        name="name"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter name"
      />
      <p>Current Name: {optimisticName}</p>
      <button type="submit">Update</button>
    </form>
  );
}

Why It’s Better: useOptimistic reduces complexity by managing temporary states during async operations.

Conclusion

React 19 empowers developers with tools like Server Components, Actions, and new hooks, making applications faster and easier to build. Upgrading from React 18 involves updating dependencies, running codemods, and testing thoroughly, but the process is manageable with the right steps. The examples above show how to leverage these features to modernize your codebase. For more details, check the official React 19 documentation at https://react.dev/blog/2024/12/05/react-19 or the upgrade guide at https://react.dev/blog/2024/04/25/react-19-upgrade-guide. Start experimenting with React 19 today to take your projects to the next level!

Vitinhhoangduc Blog

🌟 Dive into an unlimited world of knowledge! From cutting-edge technology to personal growth hacks, our blog is a treasure chest for modern minds. Whether you're a code-passionate developer, a digital-space-conquering marketer, or simply a curious explorer - let's surf the knowledge waves and ride the rhythm of the future together! 🚀 Every article is a key to new horizons - Ready to level up?