Use the narrowest state owner that serves the required consumers. Keep ephemeral interaction state local, put shareable navigation state in the URL, treat remote records as server-owned data with loading and freshness rules, and derive values instead of storing copies. Add context or an external store only when real cross-tree coordination, subscriptions, history, or tooling justify the extra model.
Who this is for: Frontend developers choosing between local component state, URL state, server data, context, and external state libraries.
- Classify each value by owner, lifetime, persistence, and synchronization needs before selecting a tool.
- Store the smallest source of truth and compute values that can be derived reliably.
- Move state outward only when multiple consumers truly need coordinated access or persistence.
Classify state before centralizing it
A menu's open flag, an unsaved form field, the current URL filter, a signed-in user, and a server record are not the same kind of state. Ask who owns the value, how long it lives, whether a reload should preserve it, whether it belongs in a shareable link, and which system is authoritative.
This classification usually reveals a natural home. Temporary visual state can live in the component that renders it. Navigation and search parameters fit the URL. Durable user preferences may use a server profile or carefully scoped browser storage. Remote domain data should not be copied into a global client object without a plan for freshness and conflicts.
Prefer one source and derived values
If a cart stores line items, subtotal can be calculated from those items. Storing both creates two values that can disagree. Derive totals during rendering unless measurement shows the calculation is expensive. Likewise, avoid state that merely mirrors a prop; decide which owner can change the value and make that ownership explicit.
Normalize only when it solves a concrete update or lookup problem. A deeply normalized store can make a small interface harder to understand, while duplicated nested data can make large collaborative views inconsistent. Choose based on update patterns, identity, and scale. Simplicity means fewer competing truths, not one prescribed data shape.
Separate remote data from interaction state
Server-owned data has network states: initial load, stale value, refresh, failure, mutation, conflict, and access loss. A data-fetching framework may manage caching and request deduplication, but it cannot decide product freshness or authorization. Keep the server authoritative and reconcile optimistic changes when the server rejects or transforms them.
Local interaction state answers a different question, such as which accordion is open or which draft tab is selected. Combining both in one broad store can rerender unrelated views and hide lifecycle behavior. A form may initialize from server data, but once edited it becomes a draft with an explicit reset, save, and conflict policy.
Escalate tools from demonstrated pressure
Lift state to the nearest shared ancestor when siblings coordinate. Use context for values needed through a subtree when passing them through many intermediate components obscures intent. Context changes can update many consumers, so split unrelated concerns. An external store is useful for independent subscriptions, complex transitions, undo, or state shared outside React.
Every added mechanism needs conventions for initialization, server rendering, persistence, testing, and debugging. Browser storage is synchronous and can contain stale or manipulated values, so do not use it for secrets or authority. Profile rerenders before introducing selectors or caches. A direct prop and local reducer often remain easier to inspect than a universal global store.
Design state for a product search page
A page has a shareable query, local filter panel, server results, saved preferences, and a draft comparison list.
- Put query, sort, and committed filters in URL parameters so back, forward, reload, and sharing behave predictably.
- Keep whether the mobile filter panel is open as local component state because it is temporary presentation.
- Treat results as server-owned data keyed by validated URL inputs, including loading, empty, error, and stale behavior.
- Store durable display preferences in the user profile, using browser storage only as a nonauthoritative fallback if needed.
- Keep the unsaved comparison list near the comparing view until another route or persistent workflow actually requires broader ownership.
State placement table
Complete one row for each independent value or state machine.
- Meaning and owner: value, authoritative system, writers, readers, and invariant.
- Lifetime: render, component, route, session, device, account, or durable domain record.
- Navigation: shareable URL, back and forward behavior, reload behavior, and default.
- Synchronization: remote freshness, optimistic update, conflict, cross-tab need, and offline behavior.
- Home and proof: local state, reducer, URL, context, server cache, external store, plus test and profiling evidence.
Common mistakes
- Putting every value into one application-wide store before identifying actual shared consumers.
- Storing calculated totals alongside their inputs and allowing the two sources to drift.
- Treating browser storage as trusted current server state or a safe location for credentials.
Try one
Place state for a checkout address form, cart total, selected delivery method, authenticated customer, and server inventory warning.
A reasoned answer keeps the editable address as a local draft, derives cart total from authoritative cart lines, stores the selected delivery method with the checkout flow, obtains customer identity from the protected session, and treats inventory warnings as server-owned responses refreshed at critical steps. It also defines reset, save, stale inventory, and submission conflict behavior.
Sources
- MDN Web Storage APIMDN's reference for origin-scoped browser storage and its synchronous behavior.
- MDN JavaScript modules guideMDN guidance for module boundaries, imports, exports, and browser module loading.
- TypeScript object types handbookThe TypeScript handbook chapter on object shapes, properties, unions, and extension.