01 — Purpose

State shapes complexity and performance

State management affects frontend complexity, maintainability, and performance directly — poor architecture creates fragile systems quickly.

Where state lives — component, URL, server cache, global store — determines how hard a feature is to change, test, and debug. The wrong default (everything in global state) spreads coupling across the codebase.

See JS module architecture, state classes (UI state in CSS), and hydration costs.

02 — Principles

State in the simplest reliable place

State should live in the simplest place capable of managing it reliably.

  • local UI state — open/closed panels, transient focus within a component
  • server state — data from APIs; cache and revalidate rather than duplicating in multiple stores
  • form state — inputs, validation, dirty flags; often colocated with the form
  • shared application state — only what multiple distant components truly need
  • persisted state — preferences, drafts; explicit sync with storage APIs

03 — Practice

Good state architecture

Minimise shared state, isolate responsibility, derive — do not duplicate.

  • minimise shared state — lift only when two branches need the same source of truth
  • isolate responsibility — one module owns a feature’s state transitions
  • derive state where possible — computed values from props and server data, not parallel copies
  • avoid duplication — one canonical value for “current user”, “cart”, “filters”

Simplicity often enough

Many interfaces only need component state, URL state (search params, routes), and server responses — not a giant application-wide state system. Add a global store when URL and props cannot coordinate the feature cleanly.

04 — Avoid

State that hurts the system

Poor state architecture creates unnecessary rendering, sync bugs, and debugging pain.

  • global state for everything — theme toggle does not need the same machinery as checkout
  • duplicated sources of truth — cart count in context and in localStorage out of sync
  • hidden mutation — shared objects mutated in place without subscribers knowing
  • tightly coupled systems — unrelated features importing the same store slice
  • unnecessary rendering — broad subscriptions that re-render half the tree
  • hydration overhead — server HTML and client state disagree on first paint — see hydration and client interactivity

05 — Close

Reduce complexity, do not centralise it

State management should reduce complexity — not centralise it.

Before adding Redux, Zustand, or a new context provider, ask whether URL params, component state, or a server cache already solve the problem. Document the decision when you do add shared state.

See frontend governance, component architecture, and JavaScript standard.

Common questions

Where should state live by default?

In the simplest place that works — component state, URL parameters, or server data — before adding shared stores. Most pages do not need global state.

When is a global store justified?

When distant parts of the UI must share one source of truth that URL and props cannot coordinate cleanly. Document why a store was added.

What causes state architecture to hurt performance?

Broad subscriptions, duplicated sources of truth, hidden mutation, and hydrating client state that fights server HTML — leading to extra renders and bugs.