Building Modern Web Applications with React
Patterns and mental models for building maintainable React apps in 2026.
Component design starts with data
Before writing any JSX, ask: what data does this component need, and where does it come from? Components that reach far for their data — through context, global state, or prop-drilling — are harder to test and harder to move.
The best components are the ones you can drop into a different part of your app with minimal changes.
Co-locate state
State should live as close as possible to where it’s used. Global state is expensive — it makes every component that reads it a dependent. Use it only when the alternative is clearly worse.
// Instead of pulling from global state:
function PostList() {
const posts = usePostsStore((s) => s.posts);
return <ul>{posts.map(p => <PostItem key={p.id} post={p} />)}</ul>;
}
// Prefer fetching at the boundary:
function PostList() {
const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
return <ul>{posts?.map(p => <PostItem key={p.id} post={p} />)}</ul>;
}
Derive, don’t sync
Syncing state across multiple sources is a common source of bugs. If a value can be derived from existing state, derive it:
// Don't:
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
// Do:
const [items, setItems] = useState([]);
const count = items.length;
Treat effects as escape hatches
useEffect is often overused. Before reaching for it, ask whether the work can happen during render, in an event handler, or in a data-fetching layer like React Query or SWR.
When you do use effects, keep them small and give them one clear job.