state-management

state-management

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.

6estrellas
1forks
Actualizado 1/23/2026
SKILL.md
readonlyread-only
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.

'use client'
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

2. URL State (useSearchParams)

Use for state that should be shareable via URL.

'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 (
    <select value={category} onChange={(e) => setCategory(e.target.value)}>
      <option value="all">All</option>
      <option value="tech">Tech</option>
      <option value="design">Design</option>
    </select>
  )
}

3. Server State (Server Components)

Use for data from your database/API that doesn't change client-side.

// Server Component (no 'use client')
export default async function UserProfile({ userId }: { userId: string }) {
  const user = await db.users.findUnique({ where: { id: userId } })

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

4. Context (React Context)

Use for simple global state (theme, user, settings) within a component tree.

'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<Theme>('light')

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  return useContext(ThemeContext)
}

// Usage in component
function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  )
}

5. Zustand (Global Store)

Use for complex global state that needs to be accessed across many components.

Basic Store:

// stores/user-store.ts
import { create } from 'zustand'

interface UserState {
  user: User | null
  isLoading: boolean
  setUser: (user: User | null) => void
  fetchUser: (id: string) => Promise<void>
  logout: () => void
}

export const useUserStore = create<UserState>((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 (
    <div>
      {isLoading ? <p>Loading...</p> : <p>{user?.name}</p>}
    </div>
  )
}

Persisted Store (localStorage):

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<PreferencesState>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language })
    }),
    {
      name: 'preferences-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
)

Sliced Stores (Organized):

// 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):

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<TodoState>()(
  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)

'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<typeof schema>

export function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm<FormData>({
    resolver: zodResolver(schema)
  })

  const onSubmit = async (data: FormData) => {
    await fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      {errors.name && <p>{errors.name.message}</p>}

      <input {...register('email')} type="email" />
      {errors.email && <p>{errors.email.message}</p>}

      <input {...register('age', { valueAsNumber: true })} type="number" />
      {errors.age && <p>{errors.age.message}</p>}

      <button type="submit" disabled={isSubmitting}>
        Submit
      </button>
    </form>
  )
}

Performance Optimization

Selective Store Subscription

// ❌ Bad - Component re-renders for any store change
function BadExample() {
  const store = useUserStore()
  return <div>{store.user?.name}</div>
}

// ✅ Good - Only re-renders when user.name changes
function GoodExample() {
  const userName = useUserStore((state) => state.user?.name)
  return <div>{userName}</div>
}

// ✅ 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 <div>{isLoading ? 'Loading...' : user?.name}</div>
}

React.memo for Components

import { memo } from 'react'

const ExpensiveComponent = memo(function ExpensiveComponent({
  data
}: {
  data: Data
}) {
  // This only re-renders when data changes
  return <div>{/* Expensive rendering */}</div>
})

useCallback for Functions

'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 <Child onClick={handleClickMemoized} />
}

Advanced Patterns

Computed Values

import { create } from 'zustand'

interface CartState {
  items: CartItem[]
  // Computed value - always fresh
  total: () => number
  subtotal: () => number
  tax: () => number
}

export const useCartStore = create<CartState>((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 <div>Total: ${total}</div>
}

Async Actions

interface DataState {
  data: Data[]
  isLoading: boolean
  error: string | null
  fetchData: () => Promise<void>
  refetch: () => Promise<void>
}

export const useDataStore = create<DataState>((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

import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
import { devtools } from 'zustand/middleware'

export const useStore = create<State>()(
  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

You Might Also Like

Related Skills

cache-components

cache-components

137Kdev-frontend

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

vercel avatarvercel
Obtener
component-refactoring

component-refactoring

128Kdev-frontend

Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

langgenius avatarlanggenius
Obtener
web-artifacts-builder

web-artifacts-builder

47Kdev-frontend

Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.

anthropics avataranthropics
Obtener
frontend-design

frontend-design

47Kdev-frontend

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

anthropics avataranthropics
Obtener
react-modernization

react-modernization

28Kdev-frontend

Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.

wshobson avatarwshobson
Obtener
tailwind-design-system

tailwind-design-system

28Kdev-frontend

Build scalable design systems with Tailwind CSS v4, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI patterns.

wshobson avatarwshobson
Obtener