Lesson 3 of 9 · 20m
Component Design: Composition, Lifting, and Escape Hatches
Where state should live, why props drilling is a design smell, and the three composition patterns that cover almost every UI.
The hard part of React isn't hooks — it's deciding where things live. Most "React is confusing" complaints are really "my state lives in the wrong place." Three moves cover nearly every situation.
Lift state to the nearest common owner
Two components need the same data? It lives in their closest common ancestor. An input and a preview? Parent owns the string. A filter and a results table? The page owns the filter. Don't reach for a store until the common ancestor is so high it would re-render the world.
Props drilling is a smell, not a sin
Passing a prop through three layers is fine; passing it through eight means the structure is wrong. Before adding context or a store, ask whether the intermediate components should exist at all — often the real fix is component composition: pass already-rendered children instead of data.
// instead of Layout taking a pile of props...
<Layout sidebar={<Sidebar user={user} />}>
<Feed />
</Layout>
// Layout never touches `user`; it just positions slots.children is the most underused prop in React. Component slots beat configuration props almost every time.
Context is for rarely-changing trees
Context's superpower is skipping layers entirely — theme, auth session, locale. Its kryptonite is frequency: every context value change re-renders every consumer. Put the provider high, the value stable (memoize objects), and keep rapidly-changing values (form state, hover, scroll) out of it.
Escape hatches, ranked by blast radius
- Callback props — child tells parent; still declarative.
ref+useImperativeHandle— child exposes a small imperative API (focus(),scrollIntoView()). Legitimate for inputs, canvases, media.- Module-level singletons — for cross-cutting non-React systems (analytics, WebSocket). Fine if the UI doesn't read it during render.
useSyncExternalStore— when external state must drive the render.
Field notes
- A component that takes 12 props usually wants to be three components.
useReducershines when transitions are more interesting than values — multi-step forms, wizards, game state.- Write the JSX you wish existed first, then implement components to satisfy it. API-first design works inside React too.