Deck 16 - React App Architecture / Component API Design (objective)
Goal: design and review React app architecture like a senior: boundaries, state placement, data flow, component APIs, and maintainable patterns.
You learn: what/why (under the hood), common anti-patterns, and evaluator-grade review wording.
Output style: PASS/PARTIAL/FAIL + 2-3 issues + fix approach.
Mental model: separation of concerns (UI vs domain vs data).
UI components render and handle interactions.
Domain logic (rules/transforms) should be testable without React.
Data layer handles fetching/caching/serialization at boundaries.
Mental model: boundaries reduce blast radius.
Well-defined boundaries make changes local.
Examples: API client boundary, feature modules, component boundaries.
Mental model: colocate state by ownership.
State should live at the lowest common ancestor of the components that need it.
Avoid lifting too high (prop drilling) and too low (duplicated state).
Mental model: derived state should be computed, not stored.
If you can derive it from props/state, compute it (useMemo when needed).
Storing derived state causes sync bugs and extra effects.
Mental model: single source of truth.
Avoid duplicate sources for the same truth (server cache + local copy + URL state).
Pick the authority and derive others from it.
Mental model: state types - server vs client vs UI.
Server state: fetched data, cached (React Query).
Client state: local app state (auth, preferences).
UI state: transient (open modal, input text).
Mental model: push side effects to the edges.
Keep components mostly pure.
Do IO in hooks/services; keep rendering code deterministic.
Mental model: dependencies flow down, events flow up.
Data and configuration typically flow down via props/context.
User actions bubble up via callbacks or state updates.
Mental model: composition over inheritance.
React encourages composing components and hooks.
Avoid class inheritance patterns; prefer composition and render props/children patterns when needed.
Mental model: component API should prevent invalid states.
Use prop types/unions to make misuse hard.
Example: Button with href vs onClick - enforce correct combinations.
Mental model: stable public interfaces matter.
Treat shared components as public APIs.
Breaking changes ripple across app; document and version patterns if needed.
Mental model: measure complexity as you design.
Complexity increases with props count, conditional branches, and cross-cutting concerns.
Split responsibilities rather than growing ‘god components’.
Mental model: file/module organization by feature.
Feature folders reduce coupling vs type-based folders.
Example: features/orders/* with components, hooks, api, tests together.
Mental model: avoid global context for everything.
Context is great for truly global concerns.
Overuse causes rerenders, coupling, and hidden dependencies.
Mental model: a ‘container/presenter’ split is a tool, not a rule.
Containers handle data and state; presenters render.
Use when it reduces complexity; do not force it everywhere.
Mental model: state machines for complex flows.
For multi-step flows (auth, checkout), model states explicitly.
Prevents impossible UI states and makes logic testable.
Mental model: URL as state (when appropriate).
Filters/search/sort/pagination often belong in URL for shareability.
Avoid putting everything in URL (sensitive data, huge payloads).
Mental model: the cost of prop drilling vs context vs store.
Prop drilling is explicit but noisy.
Context reduces plumbing but can hide dependencies.
Stores (Redux) are powerful for complex shared state; avoid for trivial UI state.
Mental model: performance is architecture too.
Bad boundaries cause unnecessary rerenders.
Design for predictable render graphs and stable props.
Pattern: lift state to lowest common ancestor (rule).
If two siblings need to coordinate, lift state to their parent.
If only one component needs it, keep it local.
Pattern: avoid syncing props into state.
Copying props to state creates stale bugs.
Prefer deriving from props or use controlled patterns.
Pattern: controlled vs uncontrolled component API.
Controlled: value + onChange from parent.
Uncontrolled: internal state with defaultValue.
Provide both only when necessary; document behavior.
Pattern: expose events, not internals.
Component APIs should communicate via callbacks (onSelect, onSubmit).
Avoid exposing internal refs/state unless needed (imperative handle).