React — rendering & architecture
React architecture is the discipline of keeping UI predictable while work is scheduled, interrupted, streamed, and hydrated. Senior React answers explain the render/commit model, data ownership, effects, boundaries, and measurement before reaching for memoization.
Core details
Render model: trigger from state, props, context, external store, action result, or server payload → React schedules work → render phase computes the next element tree → reconciliation compares Fiber trees → commit phase mutates the host environment → effects run after commit.
Stable rules:
- Hooks order is fixed per component type; no hooks in loops, conditions, or nested functions.
- Keys identify siblings; wrong keys cause state to move to the wrong row and can force expensive remounts.
- Render must stay pure: no subscriptions, DOM writes, network mutations, random persistent values, or analytics side effects.
- Effects synchronize with systems outside React: timers, subscriptions, imperative widgets, analytics, and browser APIs.
Concurrency and transitions: useTransition and startTransition mark updates as interruptible so urgent input stays responsive. Suspense reserves UI for async dependencies such as lazy code, data, or server component payloads in a meta-framework.
React 19-era mutation state: useActionState, useOptimistic, and useFormStatus keep pending/result/optimistic state close to form and action flows. These APIs improve mutation ergonomics, but they do not remove the need for idempotent server behavior and clear error states.
Memoization: React.memo, useMemo, and useCallback are tools for referential stability across optimized children or expensive calculations. They add mental overhead and should follow measurement or a clear contract.
Server Components boundary: in RSC-capable frameworks, server components can fetch and render on the server without shipping their code to the browser. Client components are explicit boundaries that hydrate and can use state, effects, event handlers, and browser APIs.
Strict Mode: development double-invokes selected render/effect paths to surface unsafe side effects. Effects and tests must be idempotent.
Understanding
React optimizes for predictable UI as a function of state, not for minimal DOM writes at any cost. The mental model is “declare UI for each state” and let the reconciler minimize commits.
Concurrency changes the story from “updates are synchronous after the handler” to “React may defer lower-priority work.” That matters for UX because typing can stay smooth while a list recalculates, but it also means effects and external integrations must not depend on fragile execution ordering.
Effects are the most common architecture smell. If an effect sets state that could have been derived during render, it creates an extra render and a synchronization problem. If an effect subscribes to an external system, it must clean up reliably and tolerate Strict Mode replays.
Context is dependency injection, not a global event bus. A single context containing user, theme, permissions, feature flags, layout state, and live data can force wide re-renders. Split providers by update frequency and consumer shape.
Practical examples
Derived state should usually stay derived:
function CartSummary({ items }: { items: CartItem[] }) {
const total = items.reduce((sum, item) => sum + item.price, 0);
return <output>{formatCurrency(total)}</output>;
}Do not copy total into state and update it in an effect unless you need a separate user-editable value.
Transitions keep urgent input responsive:
const [isPending, startTransition] = useTransition();
function onQueryChange(nextQuery: string) {
setQuery(nextQuery);
startTransition(() => {
setFilteredRows(filterRows(rows, nextQuery));
});
}The input update is urgent; the expensive result list can be interruptible.
Action-oriented mutation state:
const [result, submitAction, isPending] = useActionState(saveProfile, null);This keeps pending/result state near the form mutation path instead of scattering flags across unrelated stores.
Senior understanding
| Lens | Speak to |
|---|---|
| Operational | Hydration pairing with SSR frameworks; route-level error boundaries; production fallback logging |
| Performance | Avoid context as a mega-bus; virtualize large lists; defer heavy subtrees behind transitions |
| Product/API | Controlled vs uncontrolled inputs; lifting state vs composition; portals for stacking/focus traps |
| Architecture | Server/client boundary placement, Suspense granularity, effect ownership, testable fallback states |
Trap answers: blanket “memo everything” without profiling; blaming React for layout thrashing that is plain DOM read/write batching; using useEffect to derive state from props without a directed model.
Failure modes
- Wrong keys causing row state to move to a different item after sorting or filtering.
- Global context updates re-rendering the whole app on every keystroke.
- Non-idempotent effects sending duplicate analytics in development and hiding a real bug.
- Suspense fallback that shifts layout or hides the only useful content.
- Client component boundaries placed too high, shipping a server-renderable subtree to the browser.
Interview drill
Question: "A React app rerenders too much after a global state change. How do you fix it?"
Model answer structure:
- Profile the interaction first and identify which components render, commit, or trigger layout work.
- Check state ownership: context shape, external store selectors, derived data in render, and unstable props.
- Split providers by update frequency, use selectors, colocate local state, and keep server state in a query/cache layer.
- Add memoization only where a boundary benefits from referential stability or a calculation is measurably expensive.
- Guard with a user-visible test or profiler budget tied to the high-value interaction.
Follow-ups to expect:
- "Why is
useEffectoften a smell for derived state?" - "When does
useTransitionhelp, and when does it hide a bad architecture?" - "How do Server Components change client bundle and state boundaries?"
Diagram
See also
Mark this page when you finish learning it.
Spotted something unclear or wrong on this page?