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
1---2name: react-patterns-133description: 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---5
6# React 19 Development Patterns
7
8## Overview
9
10React 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.
11
12## When to Use
13
14- Building React 19 applications with Next.js App Router
15- Implementing optimistic UI with `useOptimistic` or `useTransition`
16- Creating Server Actions with form validation
17- Migrating from class components to hooks
18- Optimizing concurrent rendering with React Compiler
19- Managing complex state with `useReducer` or custom hooks
20- Wrapping async operations in Suspense boundaries
21
22## Quick Reference
23
24| 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 |
39
40## Instructions
41
421. **Identify Component Type**: Determine if Server Component or Client Component is needed
432. **Select Hooks**: Use appropriate hooks for state management and side effects
443. **Type Props**: Define TypeScript interfaces for all component props
454. **Handle Async**: Wrap data-fetching components in Suspense boundaries
465. **Optimize**: Use React Compiler or manual memoization for expensive renders
476. **Handle Errors**: Add ErrorBoundary for graceful error handling
487. **Validate Server Actions**: Define Zod/schema validation, then test:
49 - Submit invalid inputs → verify rejection
50 - Submit valid inputs → verify success
51
52## Examples
53
54### Server Component with Client Interaction
55
56```tsx
57// Server Component (default) — async, fetches data
58async function ProductPage({ id }: { id: string }) {
59 const product = await db.product.findUnique({ where: { id } });
60
61 return (
62 <div>
63 <h1>{product.name}</h1>
64 <AddToCartButton productId={product.id} />
65 </div>
66 );
67}
68
69// Client Component — handles interactivity
70'use client';
71function AddToCartButton({ productId }: { productId: string }) {
72 const [isPending, startTransition] = useTransition();
73
74 const handleAdd = () => {
75 startTransition(async () => {
76 await addToCart(productId);
77 });
78 };
79
80 return (
81 <button onClick={handleAdd} disabled={isPending}>
82 {isPending ? 'Adding...' : 'Add to Cart'}
83 </button>
84 );
85}
86```
87
88### useOptimistic for Instant Feedback
89
90```tsx
91'use client';
92import { useOptimistic } from 'react';
93
94function 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 );
99
100 const handleSubmit = async (formData: FormData) => {
101 const newTodo = { id: Date.now(), text: formData.get('text') as string };
102 addOptimisticTodo(newTodo); // Immediate UI update
103 await addTodo(newTodo); // Actual backend call
104 };
105
106 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```
119
120### Server Action with Form
121
122```tsx
123// app/actions.ts
124'use server';
125import { z } from 'zod';
126import { revalidatePath } from 'next/cache';
127
128const schema = z.object({
129 title: z.string().min(5),
130 content: z.string().min(10),
131});
132
133export async function createPost(prevState: any, formData: FormData) {
134 const parsed = schema.safeParse({
135 title: formData.get('title'),
136 content: formData.get('content'),
137 });
138
139 if (!parsed.success) {
140 return { errors: parsed.error.flatten().fieldErrors };
141 }
142
143 await db.post.create({ data: parsed.data });
144 revalidatePath('/posts');
145 return { success: true };
146}
147
148// app/blog/new/page.tsx
149'use client';
150import { useActionState } from 'react';
151import { createPost } from '../actions';
152
153export default function NewPostPage() {
154 const [state, formAction, pending] = useActionState(createPost, {});
155
156 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```
168
169### Custom Hook
170
171```tsx
172export function useOnlineStatus() {
173 const [isOnline, setIsOnline] = useState(true);
174
175 useEffect(() => {
176 function handleOnline() { setIsOnline(true); }
177 function handleOffline() { setIsOnline(false); }
178
179 window.addEventListener('online', handleOnline);
180 window.addEventListener('offline', handleOffline);
181
182 return () => {
183 window.removeEventListener('online', handleOnline);
184 window.removeEventListener('offline', handleOffline);
185 };
186 }, []);
187
188 return isOnline;
189}
190```
191
192### useTransition for Non-Urgent Updates
193
194```tsx
195function SearchableList({ items }: { items: Item[] }) {
196 const [query, setQuery] = useState('');
197 const [isPending, startTransition] = useTransition();
198 const [filteredItems, setFilteredItems] = useState(items);
199
200 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 };
206
207 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```
216
217## Best Practices
218
219### Server vs Client Decision
220
221- Start with Server Component (no directive needed)
222- Add `'use client'` only for: hooks, browser APIs, event handlers
223
224### State Management
225
226- Keep state minimal — compute derived values during render, not in effects
227- Use `useReducer` for state with multiple related actions
228- Lift state up to the nearest common ancestor
229
230### Effects
231
232- Use effects only for external system synchronization
233- Always specify correct dependency arrays
234- Return cleanup functions for subscriptions and timers
235- Never mutate state directly — always create new references
236
237### Performance
238
239- With React Compiler: avoid manual `useMemo`, `useCallback`, `memo`
240- Without React Compiler: use `useMemo` for expensive computations, `useCallback` for stable callbacks
241- Use `useTransition` for low-priority state updates
242- Use stable IDs as list keys, not array indices
243
244### React 19 Specifics
245
246- Wrap `use(promise)` components in Suspense boundaries
247- Use `useActionState` for form-server action integration
248- Validate Server Action inputs — they are public endpoints
249- Pass serializable data from Server to Client Components
250
251## Constraints and Warnings
252
253- **Server Components**: Cannot use hooks, event handlers, or browser APIs
254- **use() Hook**: Can only be called during render, not in callbacks or effects
255- **Server Actions**: Must include `'use server'` directive; always validate inputs
256- **State Mutations**: Never mutate state directly — always create new references
257- **Effect Dependencies**: Include all dependencies in `useEffect` dependency arrays
258- **Memory Leaks**: Always clean up subscriptions and event listeners in useEffect return
259
260## References
261
262Consult these files for detailed patterns:
263
264- **[references/hooks-patterns.md](references/hooks-patterns.md)** — useState, useEffect, useRef, useReducer, custom hooks, common pitfalls
265- **[references/component-patterns.md](references/component-patterns.md)** — Props, composition, lifting state, context, compound components, error boundaries
266- **[references/react19-features.md](references/react19-features.md)** — use(), useOptimistic, useFormStatus, useActionState, Server Actions, Server Components, migration guide
267- **[references/performance-patterns.md](references/performance-patterns.md)** — React Compiler setup, useMemo, useCallback, useTransition, useDeferredValue, lazy loading
268- **[references/typescript-patterns.md](references/typescript-patterns.md)** — Typed props, generic components, event handlers, discriminated unions, context typing
269- **[references/learn.md](references/learn.md)** — Progressive learning guide from basics to advanced React 19
270- **[references/reference.md](references/reference.md)** — Complete API reference for all React hooks and component APIs