Patterns

Patterns — React 19

Lifting State Up

When to use: two or more sibling components need to reflect or coordinate the same value. How: remove the state from the children, add it to their closest common parent, pass the value and an updater callback down as props. Trade-offs: the parent grows a bit more complex; children become fully controlled (no local state for that concern) and easier to test in isolation. See Ch 3, Ch 29.

Derive, Don't Duplicate

When to use: a value can be computed from state/props you already have. How: compute it inline in the render body (or via useMemo if the computation is expensive) instead of storing it in its own useState. Trade-offs: avoids sync bugs entirely; the only cost is recomputation, which is usually cheap and can be memoized if not. See Ch 28, Ch 37.

Reducer + Context for Shared Mutable State

When to use: several components across a subtree both read and update related state, and prop-drilling both the value and the updater is unwieldy. How: centralize update logic in a reducer (Ch 31), split state and dispatch into two Context objects, wrap the subtree in both providers, wrap consumption in small custom Hooks (useX(), useXDispatch()). Trade-offs: more boilerplate than plain useState; pays off once update logic and consumer count both grow. See Ch 33.

Custom Hook Extraction

When to use: the same stateful logic (not just markup) is duplicated across components, or a piece of Effect logic is complex enough that naming it improves readability. How: write a function starting with use that calls other Hooks internally, returns whatever the caller needs; follows the same Rules of Hooks as any built-in Hook. Trade-offs: shares behavior, not state — each call site gets independent state. See Ch 41.

Key-Based Reset

When to use: a component's internal state needs to fully reset when its logical identity changes (switching the recipient of a chat input, switching form subjects), without manually clearing every state variable. How: give the component a key derived from the identity value (<Chat key={recipient.id} />); changing the key forces React to unmount and remount. Trade-offs: simpler and more complete than a manual reset Effect, but does incur a full remount (losing any state you didn't intend to reset too). See Ch 30, Ch 37.

Effect Event for Non-Reactive Reads

When to use: an Effect needs to read a value that must always be current, but shouldn't trigger the Effect to re-run when it changes. How: wrap that logic in useEffectEvent, call it from inside the Effect; never list the Effect Event itself as a dependency. Trade-offs: solves a real tension the dependency array can't express on its own — misuse (calling it outside an Effect) breaks its guarantees. See Ch 39, Ch 54.

Optimistic Updates for Async Actions

When to use: an async Action (form submission, mutation) should feel instant rather than waiting on the network round-trip. How: useOptimistic(value, reducer?) shows a hoped-for state while the real Action is pending, then reconciles automatically to the real result on success; failures must be handled explicitly by the surrounding code. Trade-offs: better perceived performance at the cost of needing explicit failure-state handling, since reconciliation on error isn't automatic. See Ch 60.

Streaming SSR with Suspense Boundaries

When to use: a server-rendered page has some fast, always-ready content and some slow, data-dependent content, and you don't want the fast part to wait on the slow part. How: wrap the slow parts in Suspense boundaries; use renderToPipeableStream/renderToReadableStream with onShellReady to start streaming once the shell (non-suspended content) is ready, letting suspended sections stream in as their data resolves. Trade-offs: faster Time to First Byte at the cost of progressive (not all-at-once) content arrival, which matters for consumers (like some crawlers) that need complete HTML in one response. See Ch 70, Ch 106, Ch 107.

Server/Client Component Boundary Design

When to use: designing an RSC app's component tree. How: default every component to a Server Component (no directive); mark 'use client' only on the specific components that genuinely need interactivity/state/browser APIs; keep that boundary as low/small as possible to minimize client bundle size; pass data down from Server to Client Components as serializable props. Trade-offs: pushing the client boundary too high pulls unnecessary code into the browser bundle; pushing it too low can fragment interactive UI awkwardly across many small client islands. See Ch 110, Ch 112.

Resource Preloading for Perceived Speed

When to use: a component knows it (or a likely next screen) will need a specific resource (font, script, connection) soon. How: call preload/preinit/preconnect/prefetchDNS during render — calls are automatically deduplicated across components. Trade-offs: pure upside as a hint (browser decides actual scheduling), but only useful when you can actually predict the need ahead of time. See Ch 105.