Skip to content

Web Development · React

React 19: Everything You Need to Know

A comprehensive guide to React 19's new features including Server Components, Actions API, React Compiler, and the new hooks that are changing how we build React applications.

Anurag Verma

Anurag Verma

8 min read

React 19: Everything You Need to Know

Share

React 19 represents the biggest evolution of React since hooks were introduced. With Server Components now stable, a built-in compiler, and powerful new APIs, React development in 2026 looks fundamentally different. Here’s your complete guide.

React 19 New Features React 19 introduces Server Components, Actions, and a new compiler

Release Timeline

  • April 2024: React 19 Beta released
  • December 2024: React 19 stable released
  • June 2025: React 19.1 with refinements
  • October 2025: React 19.2 with Activity, Partial Pre-rendering

The React Compiler: Automatic Optimization

One of the most impactful features in React 19 is the built-in compiler that automatically optimizes your components.

What It Does

The React Compiler transforms your components into highly optimized JavaScript, handling:

  • Automatic memoization - No more manual useMemo and useCallback
  • Smart re-rendering - Only updates what actually changed
  • Bundle optimization - Smaller, faster code

Before React 19

// Manual optimization required
import { useMemo, useCallback, memo } from 'react';

const ExpensiveList = memo(({ items, onItemClick }) => {
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

With React 19 Compiler

// Compiler handles optimization automatically
const ExpensiveList = ({ items, onItemClick }) => {
  const sortedItems = [...items].sort((a, b) =>
    a.name.localeCompare(b.name)
  );

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
};

// The compiler automatically:
// - Memoizes sortedItems computation
// - Optimizes the onClick handler
// - Prevents unnecessary re-renders

React Compiler Flow The React Compiler automatically optimizes your components at build time

Server Components: Production Ready

React Server Components are now fully stable and production-ready. They represent a fundamental shift in how we think about React components.

Server vs Client Components

Component Types in React 19
├── Server Components (default)
│   ├── Run only on the server
│   ├── Zero JavaScript sent to client
│   ├── Direct database access
│   └── Async by default

└── Client Components ('use client')
    ├── Run on client (and server for SSR)
    ├── Interactive and stateful
    ├── Event handlers
    └── Browser APIs

Server Component Example

// app/products/page.jsx (Server Component by default)
import { db } from '@/lib/database';
import { ProductCard } from './ProductCard';

// This component runs on the server
// No JavaScript shipped to the client
async function ProductsPage() {
  // Direct database query - no API needed!
  const products = await db.products.findMany({
    where: { isActive: true },
    orderBy: { createdAt: 'desc' },
  });

  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

export default ProductsPage;

Client Component Example

// components/AddToCart.jsx
'use client'; // This directive makes it a Client Component

import { useState } from 'react';
import { addToCart } from '@/actions/cart';

export function AddToCart({ productId }) {
  const [isLoading, setIsLoading] = useState(false);

  async function handleClick() {
    setIsLoading(true);
    await addToCart(productId);
    setIsLoading(false);
  }

  return (
    <button onClick={handleClick} disabled={isLoading}>
      {isLoading ? 'Adding...' : 'Add to Cart'}
    </button>
  );
}

Actions API: Simplified Data Mutations

React 19 introduces Server Actions that replace traditional REST/GraphQL APIs for many use cases.

Form Actions

// Traditional approach
function ContactForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState(null);

  async function handleSubmit(e) {
    e.preventDefault();
    setIsSubmitting(true);
    try {
      const formData = new FormData(e.target);
      await fetch('/api/contact', {
        method: 'POST',
        body: formData,
      });
    } catch (err) {
      setError(err.message);
    } finally {
      setIsSubmitting(false);
    }
  }

  return <form onSubmit={handleSubmit}>...</form>;
}
// React 19 with Server Actions
// actions/contact.js
'use server';

export async function submitContact(formData) {
  const email = formData.get('email');
  const message = formData.get('message');

  await db.contacts.create({
    data: { email, message }
  });

  return { success: true };
}

// components/ContactForm.jsx
import { submitContact } from '@/actions/contact';

function ContactForm() {
  return (
    <form action={submitContact}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

Server Actions Flow Server Actions simplify data mutations without separate API endpoints

New Hooks in React 19

useActionState

Manages the state of form actions:

import { useActionState } from 'react';
import { updateProfile } from '@/actions/profile';

function ProfileForm() {
  const [state, formAction, isPending] = useActionState(
    updateProfile,
    { message: '' }
  );

  return (
    <form action={formAction}>
      <input name="name" disabled={isPending} />
      <button disabled={isPending}>
        {isPending ? 'Saving...' : 'Save'}
      </button>
      {state.message && <p>{state.message}</p>}
    </form>
  );
}

useFormStatus

Access form state from child components:

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method } = useFormStatus();

  return (
    <button disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

// Use in any form
<form action={submitAction}>
  <input name="email" />
  <SubmitButton /> {/* Knows if form is submitting */}
</form>

useOptimistic

Optimistic updates made simple:

import { useOptimistic } from 'react';

function MessageList({ messages }) {
  const [optimisticMessages, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage) => [...state, newMessage]
  );

  async function sendMessage(formData) {
    const message = formData.get('message');

    // Immediately show the message
    addOptimistic({ text: message, sending: true });

    // Then actually send it
    await submitMessage(message);
  }

  return (
    <>
      {optimisticMessages.map((msg, i) => (
        <div key={i} style={{ opacity: msg.sending ? 0.5 : 1 }}>
          {msg.text}
        </div>
      ))}
      <form action={sendMessage}>
        <input name="message" />
        <button>Send</button>
      </form>
    </>
  );
}

use() Hook

Await promises directly in components:

import { use, Suspense } from 'react';

// Create a promise
const dataPromise = fetch('/api/data').then(r => r.json());

function DataDisplay() {
  // use() unwraps the promise
  const data = use(dataPromise);

  return <div>{data.title}</div>;
}

// Wrap with Suspense for loading state
<Suspense fallback={<Loading />}>
  <DataDisplay />
</Suspense>

React 19.2 Features (October 2025)

Activity Component

Control rendering priorities with activities:

import { Activity } from 'react';

function App() {
  return (
    <Activity mode={isVisible ? 'visible' : 'hidden'}>
      <ExpensiveComponent />
    </Activity>
  );
}

// Hidden activities:
// - Keep state preserved
// - Don't render to DOM
// - Resume instantly when visible

Partial Pre-rendering

Pre-render static parts, stream dynamic content:

// Static shell is pre-rendered at build time
// Dynamic content streams in at request time

export default function ProductPage({ params }) {
  return (
    <div>
      {/* Static - pre-rendered */}
      <Header />
      <ProductDetails id={params.id} />

      {/* Dynamic - streamed */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews id={params.id} />
      </Suspense>

      {/* Static - pre-rendered */}
      <Footer />
    </div>
  );
}

Concurrent Rendering by Default

React 19 enables concurrent rendering by default, allowing React to:

  • Interrupt long renders - Keeps UI responsive
  • Prioritize updates - User input > background work
  • Batch updates intelligently - Fewer re-renders
// React 19 automatically handles this
function SearchResults({ query }) {
  const results = use(searchAPI(query));

  // React can pause this render if user types again
  // Preventing UI from freezing during searches

  return (
    <ul>
      {results.map(result => (
        <SearchResult key={result.id} result={result} />
      ))}
    </ul>
  );
}

Migration Guide

Step 1: Update Dependencies

npm install react@19 react-dom@19

Step 2: Enable the Compiler (Optional)

// babel.config.js
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      // Compiler options
    }],
  ],
};

Step 3: Migrate to Server Components

// Before: API route + client fetch
// pages/api/products.js
export default async function handler(req, res) {
  const products = await db.products.findMany();
  res.json(products);
}

// pages/products.jsx
function Products() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    fetch('/api/products')
      .then(r => r.json())
      .then(setProducts);
  }, []);

  return <ProductList products={products} />;
}
// After: Server Component
// app/products/page.jsx
async function Products() {
  const products = await db.products.findMany();
  return <ProductList products={products} />;
}

Best Practices for React 19

1. Default to Server Components

Decision Tree:
├── Does it need interactivity? → Client Component
├── Does it need browser APIs? → Client Component
├── Does it need state? → Client Component
└── Otherwise → Server Component (default)

2. Keep Client Components Small

// Good: Small client boundary
function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Only AddToCart is a Client Component */}
      <AddToCart productId={product.id} />
    </div>
  );
}

3. Use Actions for Mutations

// Prefer Actions over API routes for forms
<form action={serverAction}>
  {/* Form fields */}
</form>

Summary

React 19 brings:

FeatureBenefit
React CompilerAutomatic optimization
Server ComponentsZero-JS server rendering
Actions APISimplified data mutations
New HooksBetter form & async handling
Concurrent RenderingResponsive UIs by default

The ecosystem is still evolving, but React 19 sets the foundation for faster, simpler, and more efficient React applications.


Resources

Need help migrating to React 19? Contact CODERCOPS for expert React development services.

Enjoyed it? Pass it on.

Share this article.

The dispatch

Working notes from
the studio.

A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.

No spam, ever. Unsubscribe anytime.

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.