React Design Patterns
Advanced React patterns for state management, concurrent rendering, React 19 Actions, and Server Components
Other React Sheets
Sign in to mark items as known and track your progress.
Sign inState Management Patterns
Patterns for managing complex state with reducers, context, and custom hooks
useReducer Pattern
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' });Reducer + Context Pattern
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>
);
}Custom Hooks Composition
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
useTransition
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>
);useDeferredValue
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
useActionState
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>
);useOptimistic
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 });
}useFormStatus & form Actions
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
Server Components Basics
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>
);
}Server Actions ('use server')
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
Polymorphic Components (as prop)
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>Controlled vs Uncontrolled
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} />Provider Pattern with Custom Hooks
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;
}