---
name: state-management
description: Expert guide for React state management with Zustand, Context, and modern patterns. Use when managing global state, forms, complex UI state, or optimizing re-renders.
---
# State Management Skill
## Overview
This skill helps you choose and implement the right state management solution for your React/Next.js application. From local state to global stores, this covers all the patterns you need.
## State Management Hierarchy
### 1. Local State (useState)
Use for component-specific state that doesn't need to be shared.
```typescript
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
```
### 2. URL State (useSearchParams)
Use for state that should be shareable via URL.
```typescript
'use client'
import { useSearchParams, useRouter } from 'next/navigation'
export function SearchFilter() {
const router = useRouter()
const searchParams = useSearchParams()
const category = searchParams.get('category') || 'all'
const setCategory = (cat: string) => {
const params = new URLSearchParams(searchParams)
params.set('category', cat)
router.push(`?${params.toString()}`)
}
return (
)
}
```
### 3. Server State (Server Components)
Use for data from your database/API that doesn't change client-side.
```typescript
// Server Component (no 'use client')
export default async function UserProfile({ userId }: { userId: string }) {
const user = await db.users.findUnique({ where: { id: userId } })
return (
{user.name}
{user.email}
)
}
```
### 4. Context (React Context)
Use for simple global state (theme, user, settings) within a component tree.
```typescript
'use client'
import { createContext, useContext, useState, ReactNode } from 'react'
type Theme = 'light' | 'dark'
const ThemeContext = createContext<{
theme: Theme
setTheme: (theme: Theme) => void
}>({ theme: 'light', setTheme: () => {} })
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState('light')
return (
{children}
)
}
export function useTheme() {
return useContext(ThemeContext)
}
// Usage in component
function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
)
}
```
### 5. Zustand (Global Store)
Use for complex global state that needs to be accessed across many components.
**Basic Store:**
```typescript
// stores/user-store.ts
import { create } from 'zustand'
interface UserState {
user: User | null
isLoading: boolean
setUser: (user: User | null) => void
fetchUser: (id: string) => Promise
logout: () => void
}
export const useUserStore = create((set) => ({
user: null,
isLoading: false,
setUser: (user) => set({ user }),
fetchUser: async (id) => {
set({ isLoading: true })
try {
const response = await fetch(`/api/users/${id}`)
const user = await response.json()
set({ user, isLoading: false })
} catch (error) {
set({ isLoading: false })
}
},
logout: () => set({ user: null })
}))
// Usage in component
'use client'
import { useUserStore } from '@/stores/user-store'
export function UserProfile() {
const { user, isLoading, fetchUser } = useUserStore()
return (
}
// ✅ Good - Only re-renders when user.name changes
function GoodExample() {
const userName = useUserStore((state) => state.user?.name)
return
{userName}
}
// ✅ Better - Use shallow comparison for multiple values
import { shallow } from 'zustand/shallow'
function BetterExample() {
const { user, isLoading } = useUserStore(
(state) => ({ user: state.user, isLoading: state.isLoading }),
shallow
)
return
{isLoading ? 'Loading...' : user?.name}
}
```
### React.memo for Components
```typescript
import { memo } from 'react'
const ExpensiveComponent = memo(function ExpensiveComponent({
data
}: {
data: Data
}) {
// This only re-renders when data changes
return
{/* Expensive rendering */}
})
```
### useCallback for Functions
```typescript
'use client'
import { useCallback } from 'react'
export function Parent() {
const [count, setCount] = useState(0)
// ❌ Bad - New function on every render
const handleClick = () => {
console.log('clicked')
}
// ✅ Good - Stable function reference
const handleClickMemoized = useCallback(() => {
console.log('clicked')
}, [])
return
}
```
## Advanced Patterns
### Computed Values
```typescript
import { create } from 'zustand'
interface CartState {
items: CartItem[]
// Computed value - always fresh
total: () => number
subtotal: () => number
tax: () => number
}
export const useCartStore = create((set, get) => ({
items: [],
total: () => {
const items = get().items
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
},
subtotal: () => {
return get().total()
},
tax: () => {
return get().subtotal() * 0.1
}
}))
// Usage - recalculates on every call
function Cart() {
const total = useCartStore((state) => state.total())
return
Total: ${total}
}
```
### Async Actions
```typescript
interface DataState {
data: Data[]
isLoading: boolean
error: string | null
fetchData: () => Promise
refetch: () => Promise
}
export const useDataStore = create((set, get) => ({
data: [],
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true, error: null })
try {
const response = await fetch('/api/data')
const data = await response.json()
set({ data, isLoading: false })
} catch (error) {
set({ error: error.message, isLoading: false })
}
},
refetch: async () => {
await get().fetchData()
}
}))
```
### Middleware Composition
```typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
import { devtools } from 'zustand/middleware'
export const useStore = create()(
devtools(
persist(
immer((set) => ({
// Your store
})),
{ name: 'my-store' }
)
)
)
```
## Decision Tree
```
Do you need state?
├─ Only in this component? → useState
├─ Pass to 2-3 child components? → props
├─ Shareable URL? → useSearchParams
├─ From database/API?
│ ├─ Static/rarely changes? → Server Component
│ └─ Dynamic/frequent updates? → React Query/SWR
├─ Simple global (theme, user)? → Context
└─ Complex global/many subscribers? → Zustand
```
## Best Practices Checklist
- [ ] Start with local state (useState)
- [ ] Use URL state for shareable filters/tabs
- [ ] Prefer Server Components for DB data
- [ ] Use Context for simple global state
- [ ] Use Zustand for complex global state
- [ ] Select only needed store values
- [ ] Use shallow comparison for multiple values
- [ ] Persist user preferences to localStorage
- [ ] Handle loading and error states
- [ ] Use React Hook Form for complex forms
- [ ] Memoize expensive computations
- [ ] Use TypeScript for type safety
## Common Mistakes to Avoid
1. **Over-using global state** - Start local, move to global when needed
2. **Not selecting store values** - Always use selectors to prevent unnecessary re-renders
3. **Storing derived values** - Compute on-the-fly instead
4. **Not handling loading states** - Always show feedback to users
5. **Putting everything in Zustand** - Use the right tool for the job
## When to Use This Skill
Invoke this skill when:
- Choosing a state management solution
- Setting up Zustand stores
- Optimizing component re-renders
- Managing form state
- Implementing global user settings
- Debugging state-related issues
- Migrating from Redux to Zustand
- Setting up persisted state