Context, Global State & State Architecture
Choose between local state, lifted state, context, reducers, external stores, URL state, and server state without turning the application into an unmaintainable global store.
Context: Dependency Injection for the Tree
Context lets a component receive a value from a distant ancestor without passing it through every intermediate component. It is useful for values that are conceptually shared across a subtree, such as theme, locale, authentication context, or feature configuration.
Context is not automatically a global state-management solution. Updating a context value can cause consumers to re-render, and putting rapidly changing large objects into one context can create unnecessary work.
Split contexts by responsibility when needed. A theme context should not necessarily also contain rapidly changing application data.
const ThemeContext = createContext("light");
function App() {
return (
<ThemeContext.Provider value="dark">
<Dashboard />
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext);
return <button data-theme={theme}>Save</button>;
}- Context solves tree-wide dependency passing.
- Context is not automatically a replacement for every state store.
- Split contexts when update frequency or ownership differs.
Choosing State Location
Ask three questions: who owns this data, who needs it, and where does it persist?
Local UI state such as an open dropdown should usually remain local. State shared by sibling components can be lifted. Data needed across distant branches may justify context or an external store. State that should survive refresh or be shareable through a link may belong in the URL or server.
A common architectural failure is making every value global because global state feels convenient. Global state increases coupling and makes ownership harder to reason about.
- State location is an architecture decision.
- Prefer the narrowest ownership that satisfies requirements.
- URL state and server state are different categories from ephemeral UI state.
External Stores & Subscription Model
External stores are useful when state must be shared across many components or across non-React code. A store should expose a predictable subscription model so React can read a snapshot and update consumers when the relevant state changes.
Avoid duplicating the same data in both a store and many component states. Choose one source of truth and derive views from it.
Modern React provides APIs designed for safely subscribing to external stores, helping React coordinate updates with its rendering model.
- External stores are appropriate for shared client state with clear ownership.
- React needs a consistent snapshot/subscription contract.
- Do not create multiple competing sources of truth.
Server State vs Client State
Server state has different semantics from local UI state. It has ownership outside the browser, can become stale, may need caching, deduplication, retries, pagination, invalidation, optimistic updates, and synchronization.
Treating server data as a giant useState object often creates a lot of manual lifecycle code. A dedicated data-fetching/cache library can provide a more appropriate model.
Client state answers questions such as 'is the modal open?' Server state answers questions such as 'what data does the server currently say exists?'
A cache of remote data is not the same thing as a global UI store. Their invalidation, freshness, and ownership rules differ.
- Remote data has freshness and invalidation concerns.
- Avoid manually rebuilding a server-state cache unless requirements are simple.
- Keep ephemeral UI state separate from remote data.