React Design Patterns
Advanced React patterns for state management, concurrent rendering, React 19 Actions, and Server Components
Other React Sheets
State Management Patterns
Patterns for managing complex state with reducers, context, and custom hooks
Manage complex state transitions with a reducer function and dispatched actions
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
default: throw Error('Unknown action: ' + action.type);
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'increment' });Combine useReducer with Context to share complex state across the component tree (Redux-lite)
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(reducer, []);
return (
<TasksContext value={tasks}>
<TasksDispatchContext value={dispatch}>
{children}
</TasksDispatchContext>
</TasksContext>
);
}Build complex behavior by composing smaller, focused custom hooks
function useDebouncedSearch(query, delay = 300) {
const debounced = useDebounce(query, delay);
const { data, loading } = useFetch(`/api/search?q=${debounced}`);
return { results: data, loading };
}Concurrent Rendering
Use React concurrent features to keep the UI responsive during expensive updates
Mark state updates as non-urgent transitions to keep the UI responsive
const [isPending, startTransition] = useTransition();
function handleClick() {
startTransition(() => {
setTab('posts'); // non-urgent update
});
}
return (
<div>
{isPending && <Spinner />}
<Content tab={tab} />
</div>
);Defer rendering of expensive UI based on a value while keeping inputs responsive
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ResultsList query={deferredQuery} className={isStale ? 'stale' : ''} />
</>
);
}React 19 Actions & Forms
New form handling pattern with built-in pending state, errors, and optimistic updates
Manage form state, errors, and pending status with a single hook
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const result = await updateUser(formData.get('name'));
return result.error ? result : { success: true };
},
null
);
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>Save</button>
{state?.error && <p>{state.error}</p>}
</form>
);Show instant UI feedback while async operations are still in flight
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
async function handleAdd(text) {
addOptimisticTodo({ id: 'temp', text });
await api.addTodo({ text });
}Read pending state from a parent form without prop drilling
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}
function MyForm() {
return (
<form action={async (formData) => { await save(formData); }}>
<input name="title" />
<SubmitButton />
</form>
);
}Server Components
Render components on the server, fetch data directly, and mix with client components
Async components that run on the server with direct data access
// app/posts/page.tsx - Server Component (default)
async function PostsPage() {
const posts = await db.post.findMany();
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}Call server functions directly from client components and forms
// actions.ts
'use server';
export async function createPost(formData) {
await db.post.create({ data: { title: formData.get('title') } });
revalidatePath('/posts');
}
// PostForm.tsx (Client)
import { createPost } from './actions';
function PostForm() {
return <form action={createPost}><input name="title" /></form>;
}Component API Patterns
Patterns for designing flexible and reusable component APIs
One component that can render as different elements based on an "as" prop
function Box({ as: Component = 'div', ...props }) {
return <Component {...props} />;
}
<Box>div by default</Box>
<Box as="section">renders a section</Box>
<Box as="a" href="/home">renders an anchor</Box>
<Box as={Link} to="/home">renders a Link</Box>Two ways to manage component state - let parent control, or manage internally
// Controlled - parent owns the state
<Input value={value} onChange={setValue} />
// Uncontrolled - component owns the state
<Input defaultValue="hello" ref={inputRef} />Wrap context with a Provider component and a custom hook for safe consumption
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return <ThemeContext value={{ theme, setTheme }}>{children}</ThemeContext>;
}
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be inside ThemeProvider');
return ctx;
}