Overview
Every non-trivial UI is a state machine wearing a costume, and most frontend pain is state put in the wrong place. Component architecture is the set of habits that keep the costume from tearing: one-way data flow, components composed rather than configured, and state classified by what owns it — local, shared client, server cache, or URL — before choosing any library. The framework matters less than the discipline; React is the lingua franca but the questions are identical in Vue, Svelte, or Angular.
Key points
- One-way data flow is the constitution: data down via props, events up via callbacks; the moment two components write the same value from different directions, bugs become non-local.
- Classify state before storing it: server state (someone else's data, needs
caching/refetching/invalidation — TanStack Query, SWR, RTK Query), client state
(yours truly —
useState/useReducer, a store), URL state (filters, pagination, anything shareable — the router), form state (its own beast — react-hook-form). Most "we need Redux" moments dissolve once server state moves to a query cache. - Lift state to the lowest common owner, no higher: global stores are for genuinely global things (session, theme, feature flags); everything else colocated. Context is a dependency-injection mechanism, not a state manager — every consumer re-renders on change.
- Composition over configuration:
childrenand slots beat prop explosions; compound components (<Tabs>,<Tabs.Panel>) beat 30-prop monoliths; hooks extract behavior, headless components extract behavior + accessibility (Radix, React Aria). - Derive, don't duplicate: state that can be computed from other state must be
computed, not stored — the second copy always drifts. Reach for
useMemo/selectors, not anotheruseEffectsyncing copies (mostuseEffects are a design smell). - The store landscape, read coldly: Redux Toolkit (predictable, verbose, great devtools), Zustand (a store without ceremony), signals (Solid, Preact, Vue — fine-grained reactivity moving the recompute burden from vdom-diff to dependency graph). Choose per shape of writes, not fashion.
Details
Where each kind of state lives
| Kind | Smell when misplaced | Home |
|---|---|---|
| Server cache | "loading spinner flashes stale data", hand-rolled isLoading flags |
Query cache (TanStack Query / RTK Query) |
| Global client | Prop-drilled session/theme through 6 layers | Small store (Zustand/RTK) or context if write-rare |
| Local | A component's open/closed flag in Redux | useState next to its use |
| URL | Filters lost on refresh or unshareable links | Router / search params |
| Form | Field values mirrored into a store keystroke by keystroke | Form library, submitted as an event |
Rendering discipline
Re-renders are the tax on state design: a render is a pure function of props + state, so
the fix for a slow tree is structural (move state down, split components, memoize the
expensive leaf) before it is memo-everything. Profiling first, per
web performance.
Practice
- TodoMVC twice (source) — once in vanilla TypeScript (manual DOM, hand-rolled store), once in React; what the framework actually buys you becomes visible, and so does what it costs.
- Query-cache refactor (source) — take any
fetch-heavy side project and move every server interaction into TanStack Query;
count the
useState/useEffectpairs that disappear. - Compound component kata (source) — build
<Tabs>as a compound component with keyboard support, then compare with the Radix primitive; drills composition and the a11y you'd otherwise hand-roll. - RealWorld full-stack build (source) — the capstone: every state category present in one honest app.
Related
- TypeScript deep dive — types that make invalid states unrepresentable are half of state management.
- Design systems — the component library this architecture composes from.
- Atomic design — the composition hierarchy, from the design side.
- Frontend testing — behavior-first tests are what make refactoring this architecture safe.
- Rendering strategies & meta-frameworks — where this state lives when rendering moves to the server.
- Web performance — re-render cost is a state-placement problem before it is an optimization problem.
- Hexagonal reference implementation — Frontend — the house architecture that pins each state category behind a port.