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
useOptimisticvs. 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.