--- 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 (
{isLoading ?

Loading...

:

{user?.name}

}
) } ``` **Persisted Store (localStorage):** ```typescript import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware' interface PreferencesState { theme: 'light' | 'dark' language: string setTheme: (theme: 'light' | 'dark') => void setLanguage: (lang: string) => void } export const usePreferencesStore = create()( persist( (set) => ({ theme: 'light', language: 'en', setTheme: (theme) => set({ theme }), setLanguage: (language) => set({ language }) }), { name: 'preferences-storage', storage: createJSONStorage(() => localStorage) } ) ) ``` **Sliced Stores (Organized):** ```typescript // stores/slices/auth-slice.ts export const createAuthSlice = (set, get) => ({ token: null, isAuthenticated: false, login: async (credentials) => { const token = await loginAPI(credentials) set({ token, isAuthenticated: true }) }, logout: () => set({ token: null, isAuthenticated: false }) }) // stores/slices/cart-slice.ts export const createCartSlice = (set, get) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })), removeItem: (id) => set((state) => ({ items: state.items.filter(item => item.id !== id) })), total: () => { const items = get().items return items.reduce((sum, item) => sum + item.price, 0) } }) // stores/app-store.ts import { create } from 'zustand' import { createAuthSlice } from './slices/auth-slice' import { createCartSlice } from './slices/cart-slice' export const useAppStore = create((...a) => ({ ...createAuthSlice(...a), ...createCartSlice(...a) })) ``` **Immer Middleware (Immutable Updates):** ```typescript import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' interface TodoState { todos: Todo[] addTodo: (text: string) => void toggleTodo: (id: string) => void updateTodo: (id: string, text: string) => void } export const useTodoStore = create()( immer((set) => ({ todos: [], addTodo: (text) => set((state) => { state.todos.push({ id: crypto.randomUUID(), text, done: false }) }), toggleTodo: (id) => set((state) => { const todo = state.todos.find((t) => t.id === id) if (todo) todo.done = !todo.done }), updateTodo: (id, text) => set((state) => { const todo = state.todos.find((t) => t.id === id) if (todo) todo.text = text }) })) ) ``` ## Form State Management ### React Hook Form (Recommended) ```typescript 'use client' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const schema = z.object({ name: z.string().min(2), email: z.string().email(), age: z.number().min(18) }) type FormData = z.infer export function UserForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(schema) }) const onSubmit = async (data: FormData) => { await fetch('/api/users', { method: 'POST', body: JSON.stringify(data) }) } return (
{errors.name &&

{errors.name.message}

} {errors.email &&

{errors.email.message}

} {errors.age &&

{errors.age.message}

}
) } ``` ## Performance Optimization ### Selective Store Subscription ```typescript // ❌ Bad - Component re-renders for any store change function BadExample() { const store = useUserStore() return
{store.user?.name}
} // ✅ 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