DNA⚛ïļ ReactState Management Patterns
ðŸĶ–DinosaurReactArchitectureState Management

State Management Patterns

The 'what state library should I use?' question has a nuanced answer. Senior engineers choose based on architectural needs, not hype.

State Management Patterns

The first sign of a senior engineer? They don't reach for a global state library by default. The second sign? They know exactly when they should.

The State Categorization Framework

Before choosing a tool, categorize your state:

TypeExamplesWhere it belongs
UI stateModals, dropdowns, tab selectionLocal component state
Form stateInput values, validation errorsLocal or form library
Server stateAPI responses, cached dataReact Query / SWR
URL stateFilters, pagination, search paramsURL / router
Global app stateAuth, theme, feature flagsContext or external store

Most "state management problems" are actually server state problems disguised as global state. React Query eliminated the need for Redux in 80% of applications.

Local State: The Default

useState and useReducer cover more ground than most engineers realize:

function useToggle(initial = false) {
  const [state, setState] = useState(initial);
  const toggle = useCallback(() => setState((s) => !s), []);
  return [state, toggle] as const;
}
 
function useUndo<T>(initialValue: T) {
  const [state, dispatch] = useReducer(
    (s: { past: T[]; present: T }, action: { type: string; value?: T }) => {
      switch (action.type) {
        case 'SET':
          return { past: [...s.past, s.present], present: action.value! };
        case 'UNDO':
          const prev = s.past[s.past.length - 1];
          return { past: s.past.slice(0, -1), present: prev ?? s.present };
        default:
          return s;
      }
    },
    { past: [], present: initialValue }
  );
  return { state: state.present, set: (v: T) => dispatch({ type: 'SET', value: v }), undo: () => dispatch({ type: 'UNDO' }) };
}

Context: Misunderstood and Misused

Context is a dependency injection mechanism, not a state management library. The issue: any context value change re-renders every consumer.

// Anti-pattern: one giant context
const AppContext = createContext({ user: null, theme: 'light', locale: 'en' });
// Changing theme re-renders every component reading user
 
// Better: split by update frequency
const AuthContext = createContext<User | null>(null);
const ThemeContext = createContext<'light' | 'dark'>('light');

When Context Works Well

  • Values that rarely change (theme, locale, auth)
  • Scoped state for compound components
  • Dependency injection for testability

When Context Falls Apart

  • High-frequency updates (cursor position, animations)
  • Deep trees with many consumers
  • When you need selectors (consuming a slice of state)

Zustand: The Pragmatic Choice

Zustand hits the sweet spot for most applications — minimal API, no providers, built-in selectors:

import { create } from 'zustand';
 
interface CartStore {
  items: CartItem[];
  add: (item: CartItem) => void;
  remove: (id: string) => void;
  total: () => number;
}
 
const useCart = create<CartStore>((set, get) => ({
  items: [],
  add: (item) => set((s) => ({ items: [...s.items, item] })),
  remove: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
  total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));
 
// Component only re-renders when items change
function CartCount() {
  const count = useCart((s) => s.items.length);
  return <span>{count}</span>;
}

Zustand's selector model means components subscribe to slices — changing theme doesn't re-render components that only read items.

Redux Mental Model

Redux is overkill for most apps but its architecture teaches valuable concepts:

  • Single source of truth — one store, one state tree
  • State is read-only — mutations only via dispatched actions
  • Pure reducers — (state, action) → newState

Redux Toolkit removed the boilerplate problem. The remaining question is whether you need the indirection of actions and reducers, or whether direct mutations (Zustand, Jotai) are sufficient.

Decision Framework

Is it server data? → React Query / SWR
Is it URL-driven? → Search params / router state
Is it form data?  → React Hook Form / local state
Is it used by 1-2 components? → useState / useReducer
Is it shared across distant components?
  ├── Changes rarely? → Context
  └── Changes often? → Zustand / Jotai
Do you need time-travel / devtools / middleware? → Redux Toolkit

Interview Signal

Senior candidates demonstrate:

  1. Categorization — identifying state types before choosing tools
  2. Trade-off awareness — knowing Context's re-render cost, Redux's indirection overhead, Zustand's simplicity trade-offs
  3. Server state separation — treating API data differently from UI state
  4. Minimal global state — defaulting to local, escalating only with justification