React 19 Development Patterns
Overview
React 19 patterns for Next.js App Router, Server Actions, optimistic UI, and concurrent features. See Quick Reference for API summary and Examples for copy-paste patterns.
When to Use
- Building React 19 applications with Next.js App Router
- Implementing optimistic UI with
useOptimistic or useTransition
- Creating Server Actions with form validation
- Migrating from class components to hooks
- Optimizing concurrent rendering with React Compiler
- Managing complex state with
useReducer or custom hooks
- Wrapping async operations in Suspense boundaries
Quick Reference
| Pattern |
Hook / API |
Use Case |
| Local state |
useState |
Simple component state |
| Complex state |
useReducer |
Multi-action state machines |
| Side effects |
useEffect |
Subscriptions, data fetching |
| Shared state |
useContext / createContext |
Cross-component data |
| DOM access |
useRef |
Focus, measurements, timers |
| Performance |
useMemo / useCallback |
Expensive computations |
| Non-urgent updates |
useTransition |
Search/filter on large lists |
| Defer expensive UI |
useDeferredValue |
Stale-while-updating |
| Read resources |
use() (React 19) |
Promises and context in render |
| Optimistic UI |
useOptimistic (React 19) |
Instant feedback on mutations |
| Form status |
useFormStatus (React 19) |
Pending state in child components |
| Form state |
useActionState (React 19) |
Server action results |
| Auto-memoization |
React Compiler |
Eliminates manual memo/callback |
Instructions
- Identify Component Type: Determine if Server Component or Client Component is needed
- Select Hooks: Use appropriate hooks for state management and side effects
- Type Props: Define TypeScript interfaces for all component props
- Handle Async: Wrap data-fetching components in Suspense boundaries
- Optimize: Use React Compiler or manual memoization for expensive renders
- Handle Errors: Add ErrorBoundary for graceful error handling
- Validate Server Actions: Define Zod/schema validation, then test:
- Submit invalid inputs → verify rejection
- Submit valid inputs → verify success
Examples
Server Component with Client Interaction
// Server Component (default) — async, fetches data
async function ProductPage({ id }: { id: string }) {
const product = await db.product.findUnique({ where: { id } });
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} />
</div>
);
}
// Client Component — handles interactivity
'use client';
function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
const handleAdd = () => {
startTransition(async () => {
await addToCart(productId);
});
};
return (
<button disabled={isPending}>
{isPending ? 'Adding...' : 'Add to Cart'}
</button>
);
}
useOptimistic for Instant Feedback
'use client';
import { useOptimistic } from 'react';
function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (t: Todo) => Promise<void> }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, { ...newTodo, pending: true }]
);
const handleSubmit = async (formData: FormData) => {
const newTodo = { id: Date.now(), text: formData.get('text') as string };
addOptimisticTodo(newTodo); // Immediate UI update
await addTodo(newTodo); // Actual backend call
};
return (
<form action={handleSubmit}>
{optimisticTodos.map(todo => (
<div key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.text}
</div>
))}
<input type="text" name="text" />
<button type="submit">Add</button>
</form>
);
}
Server Action with Form
// app/actions.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
const schema = z.object({
title: z.string().min(5),
content: z.string().min(10),
});
export async function createPost(prevState: any, formData: FormData) {
const parsed = schema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
});
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
await db.post.create({ data: parsed.data });
revalidatePath('/posts');
return { success: true };
}
// app/blog/new/page.tsx
'use client';
import { useActionState } from 'react';
import { createPost } from '../actions';
export default function NewPostPage() {
const [state, formAction, pending] = useActionState(createPost, {});
return (
<form action={formAction}>
<input name="title" placeholder="Title" />
{state.errors?.title && <span>{state.errors.title[0]}</span>}
<textarea name="content" placeholder="Content" />
<button type="submit" disabled={pending}>
{pending ? 'Publishing...' : 'Publish'}
</button>
</form>
);
}
Custom Hook
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() { setIsOnline(true); }
function handleOffline() { setIsOnline(false); }
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
useTransition for Non-Urgent Updates
function SearchableList({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [filteredItems, setFilteredItems] = useState(items);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
startTransition(() => {
setFilteredItems(items.filter(i => i.name.toLowerCase().includes(e.target.value.toLowerCase())));
});
};
return (
<div>
<input value={query} />
{isPending && <span>Filtering...</span>}
<ul>{filteredItems.map(i => <li key={i.id}>{i.name}</li>)}</ul>
</div>
);
}
Best Practices
Server vs Client Decision
- Start with Server Component (no directive needed)
- Add
'use client' only for: hooks, browser APIs, event handlers
State Management
- Keep state minimal — compute derived values during render, not in effects
- Use
useReducer for state with multiple related actions
- Lift state up to the nearest common ancestor
Effects
- Use effects only for external system synchronization
- Always specify correct dependency arrays
- Return cleanup functions for subscriptions and timers
- Never mutate state directly — always create new references
Performance
- With React Compiler: avoid manual
useMemo, useCallback, memo
- Without React Compiler: use
useMemo for expensive computations, useCallback for stable callbacks
- Use
useTransition for low-priority state updates
- Use stable IDs as list keys, not array indices
React 19 Specifics
- Wrap
use(promise) components in Suspense boundaries
- Use
useActionState for form-server action integration
- Validate Server Action inputs — they are public endpoints
- Pass serializable data from Server to Client Components
Constraints and Warnings
- Server Components: Cannot use hooks, event handlers, or browser APIs
- use() Hook: Can only be called during render, not in callbacks or effects
- Server Actions: Must include
'use server' directive; always validate inputs
- State Mutations: Never mutate state directly — always create new references
- Effect Dependencies: Include all dependencies in
useEffect dependency arrays
- Memory Leaks: Always clean up subscriptions and event listeners in useEffect return
References
Consult these files for detailed patterns:
- references/hooks-patterns.md — useState, useEffect, useRef, useReducer, custom hooks, common pitfalls
- references/component-patterns.md — Props, composition, lifting state, context, compound components, error boundaries
- references/react19-features.md — use(), useOptimistic, useFormStatus, useActionState, Server Actions, Server Components, migration guide
- references/performance-patterns.md — React Compiler setup, useMemo, useCallback, useTransition, useDeferredValue, lazy loading
- references/typescript-patterns.md — Typed props, generic components, event handlers, discriminated unions, context typing
- references/learn.md — Progressive learning guide from basics to advanced React 19
- references/reference.md — Complete API reference for all React hooks and component APIs
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: react-patterns-113description: Provides comprehensive React 19 patterns for Server Components, Server Actions, useOptimistic, useActionState, useTransition, concurrent features, Suspense boundaries, and TypeScript integration. Generates executable code patterns, validates security for public endpoints, and optimizes performance with React Compiler or manual memoization. Proactively use when building React 19 applications with Next.js App Router, implementing optimistic UI, or optimizing concurrent rendering.4---56# React 19 Development Patterns78## Overview910React 19 patterns for Next.js App Router, Server Actions, optimistic UI, and concurrent features. See Quick Reference for API summary and Examples for copy-paste patterns.1112## When to Use1314- Building React 19 applications with Next.js App Router15- Implementing optimistic UI with `useOptimistic` or `useTransition`16- Creating Server Actions with form validation17- Migrating from class components to hooks18- Optimizing concurrent rendering with React Compiler19- Managing complex state with `useReducer` or custom hooks20- Wrapping async operations in Suspense boundaries2122## Quick Reference2324| Pattern | Hook / API | Use Case |25|---------|-----------|----------|26| Local state | `useState` | Simple component state |27| Complex state | `useReducer` | Multi-action state machines |28| Side effects | `useEffect` | Subscriptions, data fetching |29| Shared state | `useContext` / `createContext` | Cross-component data |30| DOM access | `useRef` | Focus, measurements, timers |31| Performance | `useMemo` / `useCallback` | Expensive computations |32| Non-urgent updates | `useTransition` | Search/filter on large lists |33| Defer expensive UI | `useDeferredValue` | Stale-while-updating |34| Read resources | `use()` (React 19) | Promises and context in render |35| Optimistic UI | `useOptimistic` (React 19) | Instant feedback on mutations |36| Form status | `useFormStatus` (React 19) | Pending state in child components |37| Form state | `useActionState` (React 19) | Server action results |38| Auto-memoization | React Compiler | Eliminates manual memo/callback |3940## Instructions41421. **Identify Component Type**: Determine if Server Component or Client Component is needed432. **Select Hooks**: Use appropriate hooks for state management and side effects443. **Type Props**: Define TypeScript interfaces for all component props454. **Handle Async**: Wrap data-fetching components in Suspense boundaries465. **Optimize**: Use React Compiler or manual memoization for expensive renders476. **Handle Errors**: Add ErrorBoundary for graceful error handling487. **Validate Server Actions**: Define Zod/schema validation, then test:49 - Submit invalid inputs → verify rejection50 - Submit valid inputs → verify success5152## Examples5354### Server Component with Client Interaction5556```tsx57// Server Component (default) — async, fetches data58async function ProductPage({ id }: { id: string }) {59 const product = await db.product.findUnique({ where: { id } });6061 return (62 <div>63 <h1>{product.name}</h1>64 <AddToCartButton productId={product.id} />65 </div>66 );67}6869// Client Component — handles interactivity70'use client';71function AddToCartButton({ productId }: { productId: string }) {72 const [isPending, startTransition] = useTransition();7374 const handleAdd = () => {75 startTransition(async () => {76 await addToCart(productId);77 });78 };7980 return (81 <button onClick={handleAdd} disabled={isPending}>82 {isPending ? 'Adding...' : 'Add to Cart'}83 </button>84 );85}86```8788### useOptimistic for Instant Feedback8990```tsx91'use client';92import { useOptimistic } from 'react';9394function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (t: Todo) => Promise<void> }) {95 const [optimisticTodos, addOptimisticTodo] = useOptimistic(96 todos,97 (state, newTodo: Todo) => [...state, { ...newTodo, pending: true }]98 );99100 const handleSubmit = async (formData: FormData) => {101 const newTodo = { id: Date.now(), text: formData.get('text') as string };102 addOptimisticTodo(newTodo); // Immediate UI update103 await addTodo(newTodo); // Actual backend call104 };105106 return (107 <form action={handleSubmit}>108 {optimisticTodos.map(todo => (109 <div key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>110 {todo.text}111 </div>112 ))}113 <input type="text" name="text" />114 <button type="submit">Add</button>115 </form>116 );117}118```119120### Server Action with Form121122```tsx123// app/actions.ts124'use server';125import { z } from 'zod';126import { revalidatePath } from 'next/cache';127128const schema = z.object({129 title: z.string().min(5),130 content: z.string().min(10),131});132133export async function createPost(prevState: any, formData: FormData) {134 const parsed = schema.safeParse({135 title: formData.get('title'),136 content: formData.get('content'),137 });138139 if (!parsed.success) {140 return { errors: parsed.error.flatten().fieldErrors };141 }142143 await db.post.create({ data: parsed.data });144 revalidatePath('/posts');145 return { success: true };146}147148// app/blog/new/page.tsx149'use client';150import { useActionState } from 'react';151import { createPost } from '../actions';152153export default function NewPostPage() {154 const [state, formAction, pending] = useActionState(createPost, {});155156 return (157 <form action={formAction}>158 <input name="title" placeholder="Title" />159 {state.errors?.title && <span>{state.errors.title[0]}</span>}160 <textarea name="content" placeholder="Content" />161 <button type="submit" disabled={pending}>162 {pending ? 'Publishing...' : 'Publish'}163 </button>164 </form>165 );166}167```168169### Custom Hook170171```tsx172export function useOnlineStatus() {173 const [isOnline, setIsOnline] = useState(true);174175 useEffect(() => {176 function handleOnline() { setIsOnline(true); }177 function handleOffline() { setIsOnline(false); }178179 window.addEventListener('online', handleOnline);180 window.addEventListener('offline', handleOffline);181182 return () => {183 window.removeEventListener('online', handleOnline);184 window.removeEventListener('offline', handleOffline);185 };186 }, []);187188 return isOnline;189}190```191192### useTransition for Non-Urgent Updates193194```tsx195function SearchableList({ items }: { items: Item[] }) {196 const [query, setQuery] = useState('');197 const [isPending, startTransition] = useTransition();198 const [filteredItems, setFilteredItems] = useState(items);199200 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {201 setQuery(e.target.value);202 startTransition(() => {203 setFilteredItems(items.filter(i => i.name.toLowerCase().includes(e.target.value.toLowerCase())));204 });205 };206207 return (208 <div>209 <input value={query} onChange={handleChange} />210 {isPending && <span>Filtering...</span>}211 <ul>{filteredItems.map(i => <li key={i.id}>{i.name}</li>)}</ul>212 </div>213 );214}215```216217## Best Practices218219### Server vs Client Decision220221- Start with Server Component (no directive needed)222- Add `'use client'` only for: hooks, browser APIs, event handlers223224### State Management225226- Keep state minimal — compute derived values during render, not in effects227- Use `useReducer` for state with multiple related actions228- Lift state up to the nearest common ancestor229230### Effects231232- Use effects only for external system synchronization233- Always specify correct dependency arrays234- Return cleanup functions for subscriptions and timers235- Never mutate state directly — always create new references236237### Performance238239- With React Compiler: avoid manual `useMemo`, `useCallback`, `memo`240- Without React Compiler: use `useMemo` for expensive computations, `useCallback` for stable callbacks241- Use `useTransition` for low-priority state updates242- Use stable IDs as list keys, not array indices243244### React 19 Specifics245246- Wrap `use(promise)` components in Suspense boundaries247- Use `useActionState` for form-server action integration248- Validate Server Action inputs — they are public endpoints249- Pass serializable data from Server to Client Components250251## Constraints and Warnings252253- **Server Components**: Cannot use hooks, event handlers, or browser APIs254- **use() Hook**: Can only be called during render, not in callbacks or effects255- **Server Actions**: Must include `'use server'` directive; always validate inputs256- **State Mutations**: Never mutate state directly — always create new references257- **Effect Dependencies**: Include all dependencies in `useEffect` dependency arrays258- **Memory Leaks**: Always clean up subscriptions and event listeners in useEffect return259260## References261262Consult these files for detailed patterns:263264- **[references/hooks-patterns.md](references/hooks-patterns.md)** — useState, useEffect, useRef, useReducer, custom hooks, common pitfalls265- **[references/component-patterns.md](references/component-patterns.md)** — Props, composition, lifting state, context, compound components, error boundaries266- **[references/react19-features.md](references/react19-features.md)** — use(), useOptimistic, useFormStatus, useActionState, Server Actions, Server Components, migration guide267- **[references/performance-patterns.md](references/performance-patterns.md)** — React Compiler setup, useMemo, useCallback, useTransition, useDeferredValue, lazy loading268- **[references/typescript-patterns.md](references/typescript-patterns.md)** — Typed props, generic components, event handlers, discriminated unions, context typing269- **[references/learn.md](references/learn.md)** — Progressive learning guide from basics to advanced React 19270- **[references/reference.md](references/reference.md)** — Complete API reference for all React hooks and component APIs271272---273> Converted and distributed by [TomeVault](https://tomevault.io/claim/giuseppe-trisciuoglio) — claim your Tome and manage your conversions.274<!-- tomevault:4.0:skill_md:2026-04-11 -->