Use when designing state management for React features, architect agent needs recommendations, or evaluating state solutions - provides four-tier decision framework (TanStack Query for server state, Context for global client state, Zustand for complex features, useReducer for state machines) with systematic evaluation checklist, trade-off analysis, performance benchmarks, and anti-patterns - includes real Chariot platform examples and industry validation from 2025 research
Decision framework for selecting appropriate state management in React applications.
Use this skill when:
You MUST use TodoWrite before starting to track all workflow steps.
React state management follows a four-tier architecture based on the "simplest tool for the job" principle:
| Tier | Solution | Use Case | Token Count |
|---|---|---|---|
| 1 | TanStack Query | Server state (API data) | Primary |
| 2 | React Context | Global client state |
| 12 contexts |
| 3 | Zustand + Immer | Complex feature state | Rare |
| 4 | useReducer | State machines | Specialized |
Core Principle: Start with the simplest solution. Only escalate to more complex tools when simpler ones don't meet requirements.
Is it server data (API response)?
├─ YES → TanStack Query (always)
└─ NO → Is it shared across many unrelated components?
├─ YES → Does it have complex nested updates?
│ ├─ YES → Consider Zustand (rare)
│ └─ NO → React Context
└─ NO → Is it feature-scoped?
├─ YES → Is it a state machine?
│ ├─ YES → useReducer + Context
│ └─ NO → useState or Context
└─ NO → useState (component-local)
When to use (MANDATORY):
Key principle: Separate server state from client state. TanStack Query handles caching, synchronization, and background updates automatically.
Configuration:
// Global defaults
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: mToMs(5), // 5 min - data stays fresh
gcTime: mToMs(15), // 15 min - keep in cache after unused
},
},
});
Per-domain tuning:
staleTime: 30sec, gcTime: 5minstaleTime: 5min, gcTime: 15minstaleTime: 30min, gcTime: 60minAnti-patterns:
See: references/tanstack-query-patterns.md for query keys, mutations, optimistic updates
When to use:
Chariot platform examples (12 contexts):
Context splitting principle: Split by update frequency. Don't put high-frequency state in same context as low-frequency state.
URL-synced pattern (innovative):
// Drawer state read from URL
const drawerOrder = JSON.parse(searchParams.get("drawerOrder") || "[]");
// Updates modify URL for deep linking
function handleOpenDrawer(resource) {
handleSetSearchParams({
drawerType: "asset",
tabKeys: ["drawerTab"],
resource,
});
}
Benefits: Browser back/forward works, deep linking automatic, no stale state on refresh.
Anti-patterns:
React 19 Compiler impact: Context performance improves automatically with React Compiler's automatic memoization (30-60% fewer re-renders).
See: references/context-patterns.md for memoization, splitting, URL-sync
When to use (RARE - only when Context becomes unwieldy):
Chariot platform example: Query Builder feature ONLY
Why Zustand for Query Builder:
blocks[].filterGroups[].filters[]lastSavedState comparison for "unsaved changes"Pattern (Zustand + Immer):
import { immer } from "zustand/middleware/immer";
import { createWithEqualityFn } from "zustand/traditional";
export const useStore = createWithEqualityFn<State>()(
immer((set) => ({
blocks: [],
actions: {
addFilter: (blockId, groupId) =>
set((state) => {
// Immer allows "mutable" syntax
const block = state.blocks.find((b) => b.id === blockId);
if (!block) return;
const group = block.filterGroups.find((g) => g.id === groupId);
if (!group) return;
group.filters.push({ id: generateId() });
}),
},
}))
);
Selector usage (critical for performance):
// ✅ Good - selective subscription
const blocks = useStore((state) => state.blocks);
const addFilter = useStore((state) => state.actions.addFilter);
// ❌ Bad - subscribes to entire store
const { blocks, actions } = useStore();
Anti-patterns:
See: references/zustand-patterns.md for Immer middleware, selectors, deep cloning
When to use:
Chariot platform example: GraphStateProvider for graph visualization
Why useReducer for Graph State:
Pattern:
type GraphStateAction =
| { type: "SELECT_NODES"; payload: { nodes: Set<string> } }
| { type: "CLEAR_SELECTION" }
| { type: "SET_ENTITY_TYPE"; payload: { entityType: GraphEntityType } };
const graphStateReducer = (state: GraphState, action: GraphStateAction): GraphState => {
switch (action.type) {
case "SELECT_NODES":
return {
...state,
selection: { ...state.selection, nodes: action.payload.nodes },
};
case "CLEAR_SELECTION":
return {
...state,
selection: { nodes: new Set(), cluster: null, assetKey: null },
};
default:
return state;
}
};
Anti-patterns:
See: references/usereducer-patterns.md for discriminated unions, memoization
| Data Type | Solution | Why |
|---|---|---|
| API responses | TanStack Query | Caching, background sync, loading states |
| Authentication | Context | Low-frequency, app-wide, simple |
| Theme/locale | Context | Very low-frequency, simple |
| Modal open/close | Context OR URL | Depends on deep-link needs |
| Drawer navigation | URL params + Context | Deep-linking, browser history |
| Form inputs | useState / react-hook-form | Component-local, high frequency |
| Complex nested forms | Zustand + Immer | Only when Context becomes unwieldy |
| Graph/chart UI state | useReducer + Context | State machine semantics |
| Table sort/filter | URL params | Shareable, refreshable |
"For 90% of SaaS platforms, MVPs, and enterprise dashboards, start with Context. Add TanStack Query for server data. Only reach for Zustand when you have a specific problem that Context doesn't solve."
Your interpretation: Context first, Zustand only for exceptional complexity.
When user asks for state management recommendation:
Then apply the decision tree to recommend the appropriate tier.
using-tanstack-query - Deep dive on TanStack Query patternsusing-zustand-state-management - Zustand implementation patternsusing-modern-react-patterns - React 19 optimizationfrontend-react-component-generator - Component templatesdocs/FRONTEND-STATE-MANAGEMENT-ARCHITECTURE.md - Chariot platform implementation