Skip to content
AI-Native Portfolio

Modern React Patterns in 2026

·2 min read
reactnext.jstypescriptfrontend

The React 19 Landscape

React 19 shipped with the new compiler, Server Actions as stable, and the use() hook. Here's what I've learned using these in production.

Server Components: Default to Server

The mental model shift: components are server-rendered by default. Only add "use client" when you need:

  • Event handlers (onClick, onChange)
  • Browser APIs (localStorage, window)
  • Hooks that manage client state (useState, useEffect)
// Server Component (default) - can async, can fetch
async function BlogPost({ slug }: { slug: string }) {
  const post = await getPost(slug); // Direct DB access
  return <article>{post.content}</article>;
}

Actions Replace Most API Routes

Before React 19, you'd create /api/contact and fetch from the client. Now:

// actions.ts
"use server";

export async function submitContact(formData: FormData) {
  const email = formData.get("email");
  await saveToDatabase(email);
  revalidatePath("/contact");
}
// ContactForm.tsx
"use client";

import { submitContact } from "./actions";

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

No fetch, no API route, no loading state management. The form just works.

The use() Hook

use() lets you read promises and context in render. Combined with Suspense:

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);
  return comments.map(c => <Comment key={c.id} {...c} />);
}

// Parent passes the promise, Suspense handles loading
<Suspense fallback={<Skeleton />}>
  <Comments commentsPromise={fetchComments(postId)} />
</Suspense>

The Compiler: Automatic Memoization

No more useMemo, useCallback, or React.memo in most cases. The compiler handles it:

// Before: manual optimization
const MemoizedExpensive = React.memo(ExpensiveComponent);
const memoizedValue = useMemo(() => compute(data), [data]);
const memoizedCallback = useCallback(() => handle(id), [id]);

// After: just write the code
function Parent() {
  const value = compute(data);
  return <ExpensiveComponent value={value} onHandle={() => handle(id)} />;
}

What I'm Still Learning

  • When to use useOptimistic vs. local state
  • RSC caching strategies with unstable_cache
  • Streaming patterns for complex UIs

The ecosystem is still catching up, but the direction is clear: less client JavaScript, more server rendering, and the compiler handles performance.

Related Content