client-state-management

majiayu000's avatarfrom majiayu000

Guide for implementing client-side state management in React applications. Use when building state architecture, selecting state libraries (Context, Zustand, Redux, Jotai), implementing caching strategies (React Query, SWR), optimistic updates, state persistence, or optimizing re-renders. Triggers on questions about global vs local state, state normalization, or selector patterns.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 5, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive architectural guide for managing client-side state in React applications. It assists developers in selecting the optimal state management library—such as Zustand, Redux, Context API, or Jotai—while implementing efficient server-state caching with React Query or SWR. By focusing on state normalization, optimistic updates, and re-render optimization, it helps engineers build scalable, high-performance frontend architectures.

Use Cases

  • Case 1: Selecting the right state management library (e.g., Zustand vs. Redux) based on project complexity and performance requirements.
  • Case 2: Implementing server-state synchronization and caching strategies to reduce API overhead and improve data consistency.
  • Case 3: Refactoring deeply nested state into normalized structures to simplify data updates and prevent UI bugs.
  • Case 4: Designing optimistic UI updates to provide immediate user feedback during asynchronous operations.
  • Case 5: Optimizing application performance by applying selector patterns and memoization to minimize unnecessary component re-renders.
nameclient-state-management
descriptionGuide for implementing client-side state management in React applications. Use when building state architecture, selecting state libraries (Context, Zustand, Redux, Jotai), implementing caching strategies (React Query, SWR), optimistic updates, state persistence, or optimizing re-renders. Triggers on questions about global vs local state, state normalization, or selector patterns.

Client-Side State Management

Decision: Library Selection

Need Use Why
Simple shared state, <5 consumers Context API Zero dependencies, built-in
Medium complexity, performance matters Zustand 1.5kb, no boilerplate, auto re-render optimization
Large app, strict patterns needed Redux Toolkit DevTools, middleware ecosystem, time-travel
Fine-grained reactivity, atoms Jotai Bottom-up, minimal re-renders, composable
Server state (fetching/caching) React Query or SWR Deduplication, background refresh, cache

Decision: Global vs Local State

Keep Local (useState/useReducer):

  • Form input values before submission
  • UI state (open/closed, hover, focus)
  • Component-specific loading/error states

Promote to Global:

  • User session/auth
  • Theme/locale preferences
  • Data shared across 3+ unrelated components
  • State that must survive navigation

State Normalization

Flatten nested data to avoid update complexity:

// Bad: nested
{ posts: [{ id: 1, author: { id: 1, name: 'Jo' }, comments: [...] }] }

// Good: normalized
{
  posts: { byId: { 1: { id: 1, authorId: 1, commentIds: [1,2] } }, allIds: [1] },
  users: { byId: { 1: { id: 1, name: 'Jo' } } },
  comments: { byId: { 1: {...}, 2: {...} } }
}

Optimistic Updates Pattern

Update UI immediately, rollback on error:

// React Query
useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    const previous = queryClient.getQueryData(['todos'])
    queryClient.setQueryData(['todos'], old => [...old, newTodo])
    return { previous }
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previous)
  },
  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
})

State Persistence

// Zustand with persist middleware
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({ theme: 'light', setTheme: (t) => set({ theme: t }) }),
    {
      name: 'app-settings',
      storage: createJSONStorage(() => localStorage), // or sessionStorage
      partialize: (state) => ({ theme: state.theme }) // persist only specific keys
    }
  )
)

Performance: Selector Patterns

Prevent unnecessary re-renders by selecting only needed state:

// Zustand - component only re-renders when `count` changes
const count = useStore((state) => state.count)

// Jotai - selectAtom for derived slices
const nameAtom = selectAtom(userAtom, (user) => user.name)

// React Query - select option
useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  select: (data) => data.name // component only gets name
})

Reference Files

Performance Checklist

  • Selectors return minimal data needed
  • Memoize selectors with expensive computations
  • Split stores by domain (don't put everything in one store)
  • Use shallow comparison for object selections in Zustand
  • Set appropriate staleTime/cacheTime for server state
  • Avoid storing derived state (compute from source)