Siddhant DevalAuthor
Senior Full-Stack Engineer·Aug 27, 2026·14 min read
Crossing the Tree: Context, Lifting State & Global UI Architectures
Global state is the last resort, not the starting template. This article traces the full decision path — from colocating state at the lowest possible node, to lifting it to a common ancestor, to using Context as dependency injection, to choosing between Redux, Jotai, and Zustand for genuinely global UI state.
Technical Series
Frontend State Architecture
Part 3 of 8
Crossing the Tree: Context, Lifting State & Global UI Architectures
The third pillar: global state is a last resort, not a starting template. Every piece of state that escapes into a global store is state you've committed to managing for the lifetime of the application, state that can be accessed and mutated from anywhere, and state that makes bugs significantly harder to trace. The discipline is to resist that escape as long as possible.
This article traces the full decision path — from keeping state in the component that owns it, to lifting it one level up, to reaching for Context as a targeted escape, to evaluating the three major global UI state patterns: Flux/Redux, atomic state, and proxy-based state.
1. State Colocation: The Default Rule
State should live at the lowest component node that needs it.
This is not an optimization — it's the architectural default. Moving state upward should require a clear justification.
1.1 Progressive Lifting — A Worked Example
Imagine a
FilterBar component with a local search input, and a ProductGrid that needs to know the search query to filter results:The solution is to lift the state to the closest common ancestor — in this case,
App:tsx

Expand
Mental Model Check
Ask: "What is the lowest node in the tree that is an ancestor of every component that reads this state?" That node is where the state belongs. Lifting higher than that is premature globalisation.
2. Prop Drilling: When Is It Actually a Problem?
Prop drilling — passing props through intermediate components that don't use them — is often cited as a reason to reach for Context. The reality is more nuanced.
Prop drilling is acceptable when:
- The intermediary component is shallow (1–2 levels).
- The prop is semantically important to the intermediary's API (e.g., a
<Form>passingisSubmittingto a<SubmitButton>).
Prop drilling becomes genuinely painful when:
- The prop passes through 3+ components that don't use it.
- The intermediary components need to be changed when the prop's type changes.
- You're threading the same prop through separate branches of the tree.
At that point, Context is the appropriate tool — but for the right reason: eliminating coupling, not "making state global."
3. Context as Dependency Injection
The correct mental model for React Context is dependency injection, not state management.
tsx
Any component inside
ThemeProvider can call useTheme() and get the current theme without the parent explicitly threading the prop through every intermediary.Architectural Note
Context re-renders every component that calls
useContext(MyContext) whenever the Provider's value prop changes. If value is a new object literal on every parent render, every consumer re-renders — even if the actual data hasn't changed. Always memoize the value or split into separate contexts.4. Splitting StateContext from DispatchContext
The most impactful Context optimization: separate the data from the updater.
tsx
The key insight:
dispatch is a stable function reference — React guarantees it never changes across renders. So components that only dispatch actions (buttons, form handlers) consume DispatchContext and never re-render when state changes. Only components that consume StateContext re-render.tsx

Expand
5. The Flux / Redux Pattern
Redux formalizes the one-way data flow principle into an architectural pattern with four actors:
| Actor | Role |
|---|---|
| Store | Single source of truth — holds the entire application state tree |
| Action | Plain object describing what happened ({ type: 'USER_LOGGED_IN', payload: user }) |
| Reducer | Pure function: (state, action) → nextState |
| Selector | Pure function: extracts and derives a slice of state for a component |
5.1 Redux Toolkit — The Modern API
The pre-RTK Redux API required enormous boilerplate (action type constants, action creators, manual normalization). Redux Toolkit's
createSlice collapses this:typescript
Pro Tip & Optimization
Redux Toolkit's
createSelector (built on Reselect) memoizes derived data. The selectCartTotal function above runs the reduce only when cart.items changes — not on every render of every component that uses it. This is the correct place for expensive derivations over global state.
Expand
6. Atomic State: Jotai
The atomic model inverts the Redux model. Instead of one centralized store, you define independent atoms — tiny units of state. Components subscribe only to the atoms they read.
typescript
Usage in components:
tsx
7. Proxy-Based State: Zustand
Zustand uses JavaScript Proxies to track which parts of the store a component actually accesses and only re-renders that component when those parts change.
typescript
The selector
(state) => state.bears is what achieves fine-grained subscriptions. Without a selector (i.e., useStore() with no argument), the component subscribes to the entire store and re-renders on every change.8. Choosing the Right Architecture
| Criterion | Context + Reducer | Redux Toolkit | Jotai | Zustand |
|---|---|---|---|---|
| Team familiarity | React built-in | Widely known | Growing | Growing |
| Bundle size (gzipped) | 0 KB | ~16 KB (RTK + react-redux) | ~3.8 KB | ~0.8 KB |
| DevTools | Limited | Excellent | Good | Good |
| Boilerplate | Low | Medium | Low | Very low |
| Re-render granularity | Context-level | Selector-level | Atom-level | Selector-level |
| Best for | Small apps, specific domains (auth, theme) | Large teams, complex domain logic | Fine-grained atoms, derived async state | Simple to moderate global UI state |
Mental Model Check
None of these libraries solve different problems — they all solve the same problem (shared mutable state with subscriptions) with different APIs and trade-offs. The "best" choice is the one your team will use consistently and correctly. A codebase with one clearly-applied pattern is always better than one with three partially-applied ones.
9. References
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#React#Context API#Redux#Zustand#Jotai#State Management#Global State#Architecture