Skip to content
All essays
WebMarch 27, 202415 min

React 19 New Features and Improvements

Explore the latest features in React 19 and how to use them effectively.

Ü
Ümit Uz
Mobile & Full Stack Developer

React 19 brings exciting new features that improve performance, developer experience, and enable new use cases. Here's everything you need to know.

What's New in React 19?

React Compiler

The new React Compiler automatically optimizes your components.

javascript
// Before React 19 - manual memoization
const MemoizedComponent = memo(function Component({ a, b }) {
  useEffect(() => {
    console.log('Effect runs');
  }, [a, b]);

  return <div>{a} {b}</div>;
});

// With React 19 Compiler - automatic optimization
function Component({ a, b }) {
  useEffect(() => {
    console.log('Effect runs');
  }, [a, b]);

  return <div>{a} {b}</div>;
}

Server Actions

Direct server mutations from client components.

javascript
// Define server action
'use server';
async function updateProfile(formData) {
  const name = formData.get('name');
  await db.user.update({ name });
  revalidatePath('/profile');
}

// Use in client component
function ProfileForm() {
  return (
    <form action={updateProfile}>
      <input name="name" type="text" />
      <button type="submit">Update</button>
    </form>
  );
}

use() Hook

Read resources in components and hooks.

javascript
import { use } from 'react';

function Comments({ commentsPromise }) {
  // Suspend until promise resolves
  const comments = use(commentsPromise);

  return (
    <ul>
      {comments.map(comment => (
        <li key={comment.id}>{comment.text}</li>
      ))}
    </ul>
  );
}

// Parent suspends while fetching
function Post({ id }) {
  const commentsPromise = fetchComments(id);

  return (
    <Suspense fallback={<Loading />}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

Improved Server Components

Simplified Data Fetching

javascript
// Server Component
async function ProductList() {
  // Direct database access
  const products = await db.product.findMany();

  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Context in Server Components

javascript
// createContext.js
export const ThemeContext = createContext('light');

// Server Component can read context
async function Page() {
  const theme = await getThemeFromCookie();

  return (
    <ThemeContext.Provider value={theme}>
      <Content />
    </ThemeContext.Provider>
  );
}

New Forms API

useActionState Hook

javascript
import { useActionState } from 'react';

function LoginForm() {
  const [state, formAction, isPending] = useActionState(
    loginAction,
    { error: null }
  );

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      {state.error && <div>{state.error}</div>}
      <button disabled={isPending}>
        {isPending ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

useFormStatus Hook

javascript
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();

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

function MyForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <SubmitButton />
    </form>
  );
}

Enhanced Transitions

useTransition Improvements

javascript
function SearchResults() {
  const [isPending, startTransition] = useTransition({
    timeoutMs: 3000, // New: configurable timeout
  });

  const [results, setResults] = useState([]);

  const handleSearch = (query) => {
    startTransition(async () => {
      const data = await searchAPI(query);
      setResults(data);
    });
  };

  return (
    <div>
      <input onChange={(e) => handleSearch(e.target.value)} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </div>
  );
}

Improved Error Handling

Error Boundary Enhancements

javascript
function ErrorBoundary({ children, fallback }) {
  return (
    <ErrorBoundary
      fallback={<ErrorFallback />}
      onError={(error, errorInfo) => {
        // Log error
        console.error('Error:', error);
        // Send to error tracking
        trackError(error, errorInfo);
      }}
    >
      {children}
    </ErrorBoundary>
  );
}

Performance Improvements

Automatic Memoization

javascript
// React 19 Compiler automatically optimizes
function ProductList({ products, filter }) {
  const filtered = products.filter(p =>
    p.name.includes(filter)
  );

  return (
    <div>
      {filtered.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Improved Concurrent Rendering

javascript
function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <ExpensiveComponent />
    </Suspense>
  );
}

New Developer Tools

React DevTools Improvements

  • Better Server Component support
  • Timeline view for concurrent rendering
  • Props and state diffing
  • Component profiling

Bundle Size

React 19 maintains similar bundle size while adding features:

react: ~6KB gzipped
react-dom: ~24KB gzipped

Migration Guide

From React 18 to 19

javascript
// Update package.json
{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

// Update imports
// Before
import { render } from 'react-dom';

// After
import { createRoot } from 'react-dom/client';

// Before
render(<App />, document.getElementById('root'));

// After
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Breaking Changes

javascript
// Removed APIs
- ReactDOM.render (use createRoot)
- ReactDOM.unmountComponentAtNode (use root.unmount)
- ReactDOM.hydrate (use hydrateRoot)

// Updated APIs
- useEffect cleanup timing
- StrictMode behavior
- Error handling

Best Practices

DOs

  • Leverage Server Components by default
  • Use Server Actions for mutations
  • Let the Compiler optimize your code
  • Use Suspense for loading states
  • Implement proper error boundaries
  • Test concurrent features

DON'Ts

  • Manually memoize everything (Compiler does it)
  • Overuse Client Components
  • Ignore Suspense boundaries
  • Skip error handling
  • Forget about accessibility
  • Mix server and client logic incorrectly

Real-World Example

E-commerce Product Page

javascript
// Server Component
async function ProductPage({ id }) {
  const product = await db.product.findUnique({ where: { id } });

  return (
    <div>
      <h1>{product.name}</h1>
      <ProductDetails product={product} />
      <AddToCart productId={product.id} />
    </div>
  );
}

// Client Component for interactivity
'use client';
function AddToCart({ productId }) {
  const [state, formAction] = useActionState(addToCart, null);

  return (
    <form action={formAction}>
      <input type="hidden" name="productId" value={productId} />
      <button type="submit">Add to Cart</button>
      {state?.success && <div>Added!</div>}
    </form>
  );
}

Conclusion

React 19 represents a significant step forward with the Compiler, Server Actions, and improved concurrent features. Focus on using Server Components by default, leverage the Compiler for optimization, and use the new hooks for better user experience. The future of React is concurrent and server-first!

Enjoyed this essay? Share it.

Related essays

Next essay
Quantum Machine Learning: Where Quantum Computing Meets AI