Frontend architecture

State Management Without Unnecessary Complexity

Place state near its owner, derive instead of duplicate, separate server and client data, and add shared stores only for measured needs.

How this page is maintained

Written for learners, checked against the sources below, and reviewed every year. Last reviewed July 27, 2026.

Short answer

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.

  1. Put query, sort, and committed filters in URL parameters so back, forward, reload, and sharing behave predictably.
  2. Keep whether the mobile filter panel is open as local component state because it is temporary presentation.
  3. Treat results as server-owned data keyed by validated URL inputs, including loading, empty, error, and stale behavior.
  4. Store durable display preferences in the user profile, using browser storage only as a nonauthoritative fallback if needed.
  5. Keep the unsaved comparison list near the comparing view until another route or persistent workflow actually requires broader ownership.
Result: Each value has one understandable owner, and the page avoids a global store while preserving navigation, freshness, and drafts.

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

Learn this with a tutor

Tell LearnLive what you already know and what you need to do with simple state management.

Build this course