You asked Claude to help you build a shopping cart feature, and it generated a complete Redux setup for you. You look at the screen full of actions, reducers, and selectors, wondering: do I really need this much complexity?
AI tools can indeed quickly generate state management code, but is the solution it generates truly suitable for your project? This article is my rethinking of "state management choices" while working with AI-assisted development. I want to figure out: which tools AI excels at, and which ones I actually need.
Starting with a Counter: The Origin of State Management
The Simplest Requirement
Let's start with the most basic example.
// Environment: React
// Scenario: A simple counter
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
What is the "state" here?
- The number
count - It changes as users click
- Only used within this component
AI Friendliness: ⭐⭐⭐⭐⭐
Why does AI perform perfectly here?
-
useStateis the most basic pattern with abundant training data - The pattern is simple and consistent, hard to get wrong
- Generated code requires almost no modification
Conclusion: If state is only used within a single component, useState is sufficient - no other tools needed.
Requirement Upgrade: Parent-Child Communication
When state needs to be shared across multiple components, things get more complex.
// Environment: React
// Scenario: State lifting to parent component
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<Display count={count} />
<Controls count={count} setCount={setCount} />
</div>
);
}
function Display({ count }) {
return <h1>{count}</h1>;
}
function Controls({ count, setCount }) {
return (
<>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
</>
);
}
Key points:
- State "lifted" to parent component
- Passed to children via props
- What are the problems with this approach?
AI Friendliness: ⭐⭐⭐⭐
AI can correctly generate state lifting code with clear props passing logic. However, if the hierarchy gets deeper, AI might generate verbose code - it will "honestly" pass props through every layer without proactively suggesting better solutions.
First Layer of Complexity: Props Drilling Becomes Frustrating
Problem Scenario: Deeply Nested Component Tree
Imagine this component structure:
// Scenario: User info needed in multiple deeply nested components
<App>
<Layout>
<Header>
<Navigation>
<UserMenu /> {/* needs user info */}
</Navigation>
</Header>
<Sidebar>
<UserProfile /> {/* needs user info */}
</Sidebar>
<Main>
<Content>
<Article>
<AuthorInfo /> {/* needs user info */}
</Article>
</Content>
</Main>
</Layout>
</App>
The Pain of Props Drilling:
// Environment: React
// Scenario: Props drilling problem
// Every layer must pass user prop
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} />;
}
function Layout({ user }) {
return (
<>
<Header user={user} />
<Sidebar user={user} />
<Main user={user} />
</>
);
}
function Header({ user }) {
return <Navigation user={user} />;
}
function Navigation({ user }) {
return <UserMenu user={user} />;
}
function UserMenu({ user }) {
// Finally used here!
return <div>{user.name}</div>;
}
Problem Analysis:
- Layout, Header, Navigation don't need user
- But to pass it to deep components, they all must receive this prop
- Code redundancy, difficult to maintain
AI's Behavior When Generating This Code:
- ⚠️ AI will "honestly" pass props through each layer
- ⚠️ Won't proactively suggest using Context or state management
- ⚠️ Generated code "works" but isn't elegant
Solution 1: Context API
// Environment: React
// Scenario: Use Context to avoid props drilling
// Create Context
const UserContext = createContext();
// Wrap root with Provider
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
);
}
// Deep component directly consumes
function UserMenu() {
const { user } = useContext(UserContext);
return <div>{user?.name}</div>;
}
// Middle components don't need to know about user
function Layout() {
return (
<>
<Header />
<Sidebar />
<Main />
</>
);
}
Context Advantages:
- ✅ Solves Props Drilling
- ✅ Middle components don't need to handle data passing
- ✅ React native API, no extra dependencies
Context Problems:
// Environment: React
// Scenario: Performance issue with Context
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// ❌ Every time user or theme changes, all consumers re-render
return (
<UserContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</UserContext.Provider>
);
}
// Even if component only needs theme, it re-renders when user changes
function ThemeToggle() {
const { theme, setTheme } = useContext(UserContext);
// Re-renders when user changes!
}
AI Friendliness: ⭐⭐⭐
AI's Behavior When Generating Context Code:
- ✅ AI can correctly generate basic Context usage
- ⚠️ AI often ignores performance optimizations (split context, useMemo)
- ⚠️ AI might put all state in one Context
- ❌ AI-generated code needs manual review for performance issues
My experience is: After letting AI generate Context code, I need to manually check:
- Does it need to be split into multiple Contexts?
- Does the value object need useMemo?
- Are there unnecessary re-renders?
Context Use Cases:
- ✅ Infrequently changing data (theme, language, user info)
- ✅ Only needs to cross 2-3 component layers
- ✅ Simple projects, don't want extra dependencies
- ❌ Frequently changing data (form input, animations)
- ❌ Complex state update logic needed
Second Layer of Complexity: State Update Logic Gets Complex
Problem Scenario: Shopping Cart's Complex State
// Environment: React
// Scenario: Shopping cart with complex operations
function Cart() {
const [items, setItems] = useState([]);
// Add item
const addItem = (product) => {
const existing = items.find(item => item.id === product.id);
if (existing) {
setItems(items.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
setItems([...items, { ...product, quantity: 1 }]);
}
};
// Remove item
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
// Update quantity
const updateQuantity = (id, quantity) => {
setItems(items.map(item =>
item.id === id ? { ...item, quantity } : item
));
};
// Clear cart
const clearCart = () => {
setItems([]);
};
// Calculate total
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
// ... component render logic
}
Problem Analysis:
- setState logic scattered across functions
- Each function must handle immutable updates
- Complex conditional logic and array operations
- Difficult to track state changes
Solution 2: useReducer
// Environment: React
// Scenario: Manage complex state with Reducer
// Define Action Types
const ACTIONS = {
ADD_ITEM: 'ADD_ITEM',
REMOVE_ITEM: 'REMOVE_ITEM',
UPDATE_QUANTITY: 'UPDATE_QUANTITY',
CLEAR_CART: 'CLEAR_CART'
};
// Reducer: Centralized state change logic
function cartReducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_ITEM: {
const existing = state.items.find(item => item.id === action.payload.id);
if (existing) {
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
}
case ACTIONS.REMOVE_ITEM:
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case ACTIONS.UPDATE_QUANTITY:
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
)
};
case ACTIONS.CLEAR_CART:
return { ...state, items: [] };
default:
return state;
}
}
// Use in component
function Cart() {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => {
dispatch({ type: ACTIONS.ADD_ITEM, payload: product });
};
const removeItem = (id) => {
dispatch({ type: ACTIONS.REMOVE_ITEM, payload: id });
};
// State update logic centralized in reducer
// Component only dispatches actions
}
useReducer Advantages:
- ✅ State update logic centralized, easy to maintain
- ✅ Action types are explicit, easy to track
- ✅ Test-friendly (Reducer is a pure function)
- ✅ Suitable for complex state transitions
AI Friendliness: ⭐⭐⭐⭐
AI can generate well-structured Reducers. The switch-case pattern is familiar to AI. However, AI might generate overly verbose code, and the organization of action types and actions might not be elegant enough.
My experience is: AI-generated Reducer code is usually usable, but needs manual optimization:
- Extract repeated logic
- Simplify immutable updates (consider Immer)
- Optimize Action organization
Solution 3: Zustand (AI's Favorite)
// Environment: React + Zustand
// Scenario: More concise global state management
import { create } from 'zustand';
// Everything visible in one file
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => set((state) => {
const existing = state.items.find(item => item.id === product.id);
if (existing) {
return {
items: state.items.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
items: [...state.items, { ...product, quantity: 1 }]
};
}),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id)
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map(item =>
item.id === id ? { ...item, quantity } : item
)
})),
clearCart: () => set({ items: [] }),
// Derived state (auto-calculated)
get total() {
return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}));
// Use in component (very concise)
function Cart() {
const { items, addItem, removeItem, total } = useCartStore();
return (
<div>
{items.map(item => (
<CartItem key={item.id} item={item} onRemove={removeItem} />
))}
<p>Total: ${total}</p>
</div>
);
}
// Other components can easily access
function CartBadge() {
const itemCount = useCartStore(state => state.items.length);
return <span>{itemCount}</span>;
}
Zustand Advantages:
- ✅ No Provider wrapper needed
- ✅ Minimal code, everything in one file
- ✅ Great performance (component-level precise subscriptions)
- ✅ Simple API, low learning curve
- ✅ Excellent TypeScript support
Comparison with useReducer:
| Feature | useReducer | Zustand |
|---|---|---|
| Boilerplate | More | Minimal |
| Cross-component sharing | Needs Context | Native support |
| Learning curve | Medium | Low |
| DevTools | Need to implement | Built-in |
AI Friendliness: ⭐⭐⭐⭐⭐ (Highest)
Why Does AI Love Zustand?
- ✅ Single file shows complete picture, AI easily understands context
- ✅ Unified pattern, high-quality generated code
- ✅ No cross-file references, won't miss associations
- ✅ TypeScript type inference friendly, AI-generated types are accurate too
My Actual Experience:
Me: Help me write shopping cart state management with Zustand
Claude: [Generates complete, usable code]
Me: Almost no modification needed, works directly ✅
Me: Help me write shopping cart with Redux
Claude: [Generates actions, reducers, types...]
Me: Need to check associations between files, fix inconsistencies ⚠️
Zustand Use Cases:
- ✅ Small to medium projects
- ✅ Need global state but don't want to write much code
- ✅ AI-assisted development (AI generates high quality)
- ✅ Team members have varying React experience
- ⚠️ Very large projects might need stricter conventions (consider Redux)
Third Layer of Complexity: Server Data's Special Nature
Problem Scenario: Data Synchronization Dilemma
// Environment: React
// Scenario: Product list + product detail
// Problem: How to keep data consistent?
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetchProducts()
.then(setProducts)
.finally(() => setLoading(false));
}, []);
// Problem 1: Data may be stale when returning from detail page
// Problem 2: Other users modified product, I see old data
// Problem 3: Same product may show different data in list vs detail
}
function ProductDetail({ id }) {
const [product, setProduct] = useState(null);
useEffect(() => {
fetchProduct(id).then(setProduct);
}, [id]);
const updateProduct = async (data) => {
await updateProductAPI(id, data);
setProduct(data); // Update detail page
// Problem: What about the list page data?
};
}
Traditional Approach Problems:
- Data caching: When to refetch?
- Data synchronization: How do multiple components share the same data?
- Loading states: Every component needs loading/error logic
- Data staleness: How to determine when data needs refresh?
Solution 4: React Query
// Environment: React + React Query
// Scenario: Elegantly manage server state
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// List page
function ProductList() {
const { data: products, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Detail page
function ProductDetail({ id }) {
const queryClient = useQueryClient();
const { data: product } = useQuery({
queryKey: ['product', id],
queryFn: () => fetchProduct(id),
});
const updateMutation = useMutation({
mutationFn: (data) => updateProductAPI(id, data),
onSuccess: (updatedProduct) => {
// Update detail cache
queryClient.setQueryData(['product', id], updatedProduct);
// Invalidate list, trigger refetch
queryClient.invalidateQueries(['products']);
// Data auto synced!
},
});
return (
<div>
<h1>{product.name}</h1>
<button onClick={() => updateMutation.mutate(newData)}>
Update
</button>
</div>
);
}
React Query Advantages:
- ✅ Automatic cache management
- ✅ Automatic refetching (on window focus, network recovery)
- ✅ Automatic deduplication (single request when multiple components query same data)
- ✅ Optimistic updates, rollback on failure
- ✅ Pagination, infinite scroll support
- ✅ Built-in loading/error states
Division of Labor with Zustand:
| State Type | Tool Choice | Examples |
|---|---|---|
| Client State | Zustand/Context | Modal switch, theme, form draft |
| Server State | React Query | User info, product list, order data |
Important Mindset Shift:
- React Query is not a "state management library"
- It's a "server state synchronization tool"
- Server data has a special lifecycle (fetch, cache, invalidate, refetch)
AI Friendliness: ⭐⭐⭐⭐
AI's Behavior When Generating React Query Code:
- ✅ AI can generate standard useQuery/useMutation code
- ✅ Common patterns (loading, error, success) are familiar to AI
- ⚠️ Complex caching strategies might be inappropriate
- ⚠️ Optimistic update logic prone to AI errors
My experience:
- Let AI generate basic useQuery code: very high quality ✅
- Complex cache invalidation: needs manual review ⚠️
- Mutation onSuccess/onError logic: AI might not be thorough ⚠️
SWR vs React Query
// Environment: React + SWR
// Scenario: SWR syntax (more concise)
import useSWR from 'swr';
function ProductList() {
const { data, error } = useSWR('/api/products', fetcher);
// Simpler, but slightly less powerful
}
Comparison:
| Feature | React Query | SWR |
|---|---|---|
| Feature completeness | More powerful | Sufficient |
| API complexity | Slightly complex | More concise |
| Community size | Larger | Smaller |
| AI generation quality | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
AI Support for Both:
- Both are declarative APIs, AI can generate well
- SWR is simpler, AI-generated code is "cleaner"
- React Query more powerful, but AI might not use advanced features
Fourth Layer of Complexity: Do You Really Need Redux?
Redux's Position
// Environment: React + Redux Toolkit
// Scenario: Modern Redux (already much simpler)
import { createSlice, configureStore } from '@reduxjs/toolkit';
// Slice: combines actions and reducer
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
addItem: (state, action) => {
// Redux Toolkit supports "mutable" syntax (uses Immer internally)
const existing = state.items.find(item => item.id === action.payload.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
},
removeItem: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
},
},
});
// Store
const store = configureStore({
reducer: {
cart: cartSlice.reducer,
},
});
// Use in component
function Cart() {
const items = useSelector(state => state.cart.items);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(cartSlice.actions.addItem(product))}>
Add to Cart
</button>
);
}
Redux Advantages:
- ✅ Powerful DevTools (time-travel debugging)
- ✅ Strict state management conventions (suitable for large teams)
- ✅ Rich middleware ecosystem (redux-saga, redux-thunk)
- ✅ Largest community, most resources
Redux Problems:
- ❌ Even with Toolkit, still more code
- ❌ Steep learning curve
- ❌ Simple features still need complete workflow
AI Friendliness: ⭐⭐⭐ (Medium)
AI's Behavior When Generating Redux Code:
- ✅ Redux Toolkit's createSlice can be correctly generated
- ⚠️ Cross-file associations (types, actions, selectors) prone to issues
- ⚠️ Middleware, async action logic might use outdated patterns
- ❌ Large project file organization might not be reasonable
My Actual Experience:
Me: Write shopping cart with Redux Toolkit
Claude: [Generates slice, store config...]
Me: Code works, but need to check:
- Does it follow project file organization conventions?
- Do selectors need reselect optimization?
- Should async logic use createAsyncThunk?
When Do You Really Need Redux?
My thoughts (not necessarily accurate):
✅ Suitable for Redux:
- Very large projects (100+ components, 10+ developers)
- Need strict code standards and reviews
- Need time-travel debugging
- Complex state dependencies
- Need middleware (logging, analytics, access control)
❌ Don't Need Redux:
- Small to medium projects (Zustand is enough)
- Rapid iteration (Redux too heavy)
- Team lacks React experience (high learning curve)
- Mainly server data (React Query more suitable)
A Decision Criterion:
If you're not sure whether you need Redux, you probably don't need it.
— Dan Abramov (Redux creator)
AI Collaboration Advice:
- When collaborating with AI, Zustand has higher development efficiency
- Redux needs more manual review and adjustment
- Unless project truly needs Redux's strictness, prioritize Zustand
Decision Tree: How to Choose State Management Solution
Complete Decision Flow
Solution Comparison Table
| Solution | Learning Cost | Code Amount | Performance | AI Friendliness | Use Cases |
|---|---|---|---|---|---|
| useState | ⭐ | Minimal | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Single component state |
| Context | ⭐⭐ | Low | ⭐⭐⭐ | ⭐⭐⭐ | Cross-level, infrequent changes |
| useReducer | ⭐⭐ | Medium | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Complex state logic |
| Zustand | ⭐⭐ | Low | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Global state (recommended) |
| React Query | ⭐⭐⭐ | Medium | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Server data (must-have) |
| Redux | ⭐⭐⭐⭐ | High | ⭐⭐⭐⭐ | ⭐⭐⭐ | Large projects, strict standards |
My Recommended Combinations
Small Projects (personal projects, demos):
useState + Context + React Query
Medium Projects (startups, small teams):
Zustand (client state) + React Query (server data)
Large Projects (big companies, multi-team collaboration):
Redux (complex logic) + React Query (server data)
AI Collaboration Priority:
Zustand (most efficient) + React Query
Further Exploration: State Management Thinking in the AI Era
Summary of AI-Generated Code Characteristics
Through the analysis above, I've found AI has clear tendencies in state management:
AI Excels At:
- ✅ Code with unified patterns (Zustand, React Query)
- ✅ Single-file complete picture (no cross-file understanding needed)
- ✅ Declarative APIs (useQuery, useState)
- ✅ Well-structured Reducers
AI Struggles With:
- ❌ Cross-file dependencies (Redux's actions/reducers separation)
- ❌ Performance optimization details (Context split, memo)
- ❌ Complex caching strategies
- ❌ Architecture-level decisions (which tool to use)
What Will AI "Fool" You About?
Issue 1: AI Might Recommend Overly Complex Solutions
You: Help me build a todo list
AI: [Generates complete Redux solution]
Reality: useState is enough
Why?
- Redux examples are abundant in AI's training data
- AI tends to generate "complete" solutions
- But doesn't necessarily consider your project scale
Issue 2: AI Might Ignore Performance Issues
// Environment: React + Context
// Scenario: AI generated Context code
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [cart, setCart] = useState([]);
// ❌ AI may not tell you: this causes all consumers to re-render
return (
<AppContext.Provider value={{ user, theme, cart, setUser, setTheme, setCart }}>
{children}
</AppContext.Provider>
);
}
What You Should Do:
// Split into multiple Contexts
const UserContext = createContext();
const ThemeContext = createContext();
const CartContext = createContext();
Issue 3: AI Might Generate Outdated Patterns
// AI may generate old Redux pattern
const ADD_TODO = 'ADD_TODO';
function addTodo(text) {
return { type: ADD_TODO, text };
}
function todoReducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, { text: action.text }];
default:
return state;
}
}
// Actually Redux Toolkit's createSlice is more concise
How to Collaborate Better with AI
Strategy 1: Clearly Tell AI Your Project Scale
❌ Not good: Help me with state management
✅ Better: I'm building a medium-sized project (20 components),
need to manage user info and cart, use Zustand
Strategy 2: Ask AI to Explain Its Choices
You: Why choose Redux over Zustand?
AI: Because you mentioned needing time-travel debugging and middleware...
You: Oh I don't need those, let's use Zustand then
Strategy 3: Validate in Steps
Step 1: Let AI generate basic code
Step 2: Review performance and security yourself
Step 3: Let AI optimize specific parts (not full rewrite)
Strategy 4: Build Your Own Code Templates
// Save verified good code as templates
// Next time ask AI to "generate code based on this template"
// AI will mimic your template, not use its default pattern
Future Thinking
Question: How Will State Management Evolve in the AI Era?
Some of my speculations (not necessarily correct):
-
More Concise APIs
- AI-friendly tools will become more popular (Zustand, Jotai)
- Complex boilerplate tools might become obsolete
-
Intelligent State Management
- Can AI automatically determine when state management is needed?
- Can AI automatically optimize performance issues?
-
Local-First Architecture
- Offline-first applications becoming more common
- State synchronization will become more complex
- New tools and patterns needed
-
AI-Native State Design
- If we consider AI collaboration from the start
- How would state management tools be designed?
Questions to Explore:
- Will Signals (SolidJS) become mainstream?
- How will Server Components (RSC) change state management?
- How to design state for AI Agents executing multi-step tasks?
Summary
This article is more my thinking and practice in AI-assisted development.
Core Takeaways:
- State management isn't about "choosing libraries" but "understanding requirements → choosing appropriate solutions"
- AI excels at generating concise, unified code (Zustand, React Query)
- AI struggles with architectural decisions and performance optimization
- When collaborating with AI, humans need to control direction while AI executes
Practical Advice:
- Prioritize AI-friendly tools (Zustand + React Query)
- Clearly tell AI your project scale and specific needs
- Review AI-generated code (especially performance and architecture)
- Build your own code templates, let AI imitate them
Open Questions:
- What pitfalls have you encountered in AI-assisted development?
- Do you use AI-generated state management code directly or modify it?
- If you were to design an "AI-friendly" state management library, how would you do it?
References
- Zustand GitHub - Minimalist state management
- TanStack Query Documentation - Powerful server state management
- Redux Toolkit Documentation - Modern Redux approach
- SWR Documentation - Data fetching library by Vercel
- React Official Docs - useContext - Context API official documentation
- Kent C. Dodds - Application State Management with React - React state management thoughts
- Mark Erikson - Redux vs Context - Comparative analysis of Redux and Context

Top comments (0)