
React State Management
- 1 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
react-state-management is a skill that guides choosing and implementing React state with Redux Toolkit, Zustand, Jotai, and React Query.
About
This skill is a guide to modern React state management with Redux Toolkit, Zustand, Jotai, and React Query. It categorizes local, global, server, URL, and form state and gives selection criteria plus TypeScript store examples. A developer uses it when setting up global state, managing server state, or choosing between state libraries.
- Redux Toolkit, Zustand, Jotai, and React Query patterns
- Selection criteria for local, global, and server state
- TypeScript store examples with async thunks and slices
React State Management by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,912 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
react-state-management capabilities & compatibility
- Capabilities
- frontend · ui design
- Use cases
- frontend
- Pricing
- Free
What react-state-management says it does
Master modern React state management with Redux Toolkit, Zustand, Jotai, and React Query.
Comprehensive guide to modern React state management patterns, from local component state to global stores and server state synchronization.
npx skills add https://github.com/aiskillstore/marketplace --skill react-state-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Choose and set up React state management with Redux Toolkit, Zustand, Jotai, or React Query.
Who is it for?
Developers picking or wiring a React state solution across local, global, and server state.
Skip if: Non-React apps or state management outside JavaScript UI.
When should I use this skill?
Setting up global state, managing server state, or choosing between Redux Toolkit, Zustand, or Jotai.
What you get
Produces typed Redux Toolkit, Zustand, Jotai, or React Query state setups for a React app.
- typed Zustand or Redux store
- React Query setup
By the numbers
- 5-category state comparison table
- 4 state libraries compared (Redux Toolkit, Zustand, Jotai, React Query)
Files
React State Management
Comprehensive guide to modern React state management patterns, from local component state to global stores and server state synchronization.
Do not use this skill when
- The task is unrelated to react state management
- You need a different domain or tool outside this scope
Instructions
- Clarify goals, constraints, and required inputs.
- Apply relevant best practices and validate outcomes.
- Provide actionable steps and verification.
- If detailed examples are required, open
resources/implementation-playbook.md.
Use this skill when
- Setting up global state management in a React app
- Choosing between Redux Toolkit, Zustand, or Jotai
- Managing server state with React Query or SWR
- Implementing optimistic updates
- Debugging state-related issues
- Migrating from legacy Redux to modern patterns
Core Concepts
1. State Categories
| Type | Description | Solutions |
|---|---|---|
| Local State | Component-specific, UI state | useState, useReducer |
| Global State | Shared across components | Redux Toolkit, Zustand, Jotai |
| Server State | Remote data, caching | React Query, SWR, RTK Query |
| URL State | Route parameters, search | React Router, nuqs |
| Form State | Input values, validation | React Hook Form, Formik |
2. Selection Criteria
Small app, simple state → Zustand or Jotai
Large app, complex state → Redux Toolkit
Heavy server interaction → React Query + light client state
Atomic/granular updates → JotaiQuick Start
Zustand (Simplest)
// store/useStore.ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface AppState {
user: User | null
theme: 'light' | 'dark'
setUser: (user: User | null) => void
toggleTheme: () => void
}
export const useStore = create<AppState>()(
devtools(
persist(
(set) => ({
user: null,
theme: 'light',
setUser: (user) => set({ user }),
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
}),
{ name: 'app-storage' }
)
)
)
// Usage in component
function Header() {
const { user, theme, toggleTheme } = useStore()
return (
<header className={theme}>
{user?.name}
<button onClick={toggleTheme}>Toggle Theme</button>
</header>
)
}Patterns
Pattern 1: Redux Toolkit with TypeScript
// store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import userReducer from './slices/userSlice'
import cartReducer from './slices/cartSlice'
export const store = configureStore({
reducer: {
user: userReducer,
cart: cartReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST'],
},
}),
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// Typed hooks
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector// store/slices/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
interface User {
id: string
email: string
name: string
}
interface UserState {
current: User | null
status: 'idle' | 'loading' | 'succeeded' | 'failed'
error: string | null
}
const initialState: UserState = {
current: null,
status: 'idle',
error: null,
}
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId: string, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to fetch user')
return await response.json()
} catch (error) {
return rejectWithValue((error as Error).message)
}
}
)
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.current = action.payload
state.status = 'succeeded'
},
clearUser: (state) => {
state.current = null
state.status = 'idle'
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.status = 'loading'
state.error = null
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.status = 'succeeded'
state.current = action.payload
})
.addCase(fetchUser.rejected, (state, action) => {
state.status = 'failed'
state.error = action.payload as string
})
},
})
export const { setUser, clearUser } = userSlice.actions
export default userSlice.reducerPattern 2: Zustand with Slices (Scalable)
// store/slices/createUserSlice.ts
import { StateCreator } from 'zustand'
export interface UserSlice {
user: User | null
isAuthenticated: boolean
login: (credentials: Credentials) => Promise<void>
logout: () => void
}
export const createUserSlice: StateCreator<
UserSlice & CartSlice, // Combined store type
[],
[],
UserSlice
> = (set, get) => ({
user: null,
isAuthenticated: false,
login: async (credentials) => {
const user = await authApi.login(credentials)
set({ user, isAuthenticated: true })
},
logout: () => {
set({ user: null, isAuthenticated: false })
// Can access other slices
// get().clearCart()
},
})
// store/index.ts
import { create } from 'zustand'
import { createUserSlice, UserSlice } from './slices/createUserSlice'
import { createCartSlice, CartSlice } from './slices/createCartSlice'
type StoreState = UserSlice & CartSlice
export const useStore = create<StoreState>()((...args) => ({
...createUserSlice(...args),
...createCartSlice(...args),
}))
// Selective subscriptions (prevents unnecessary re-renders)
export const useUser = () => useStore((state) => state.user)
export const useCart = () => useStore((state) => state.cart)Pattern 3: Jotai for Atomic State
// atoms/userAtoms.ts
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
// Basic atom
export const userAtom = atom<User | null>(null)
// Derived atom (computed)
export const isAuthenticatedAtom = atom((get) => get(userAtom) !== null)
// Atom with localStorage persistence
export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light')
// Async atom
export const userProfileAtom = atom(async (get) => {
const user = get(userAtom)
if (!user) return null
const response = await fetch(`/api/users/${user.id}/profile`)
return response.json()
})
// Write-only atom (action)
export const logoutAtom = atom(null, (get, set) => {
set(userAtom, null)
set(cartAtom, [])
localStorage.removeItem('token')
})
// Usage
function Profile() {
const [user] = useAtom(userAtom)
const [, logout] = useAtom(logoutAtom)
const [profile] = useAtom(userProfileAtom) // Suspense-enabled
return (
<Suspense fallback={<Skeleton />}>
<ProfileContent profile={profile} onLogout={logout} />
</Suspense>
)
}Pattern 4: React Query for Server State
// hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
// Query keys factory
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
}
// Fetch hook
export function useUsers(filters: UserFilters) {
return useQuery({
queryKey: userKeys.list(filters),
queryFn: () => fetchUsers(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
})
}
// Single user hook
export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => fetchUser(id),
enabled: !!id, // Don't fetch if no id
})
}
// Mutation with optimistic update
export function useUpdateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: userKeys.detail(newUser.id) })
// Snapshot previous value
const previousUser = queryClient.getQueryData(userKeys.detail(newUser.id))
// Optimistically update
queryClient.setQueryData(userKeys.detail(newUser.id), newUser)
return { previousUser }
},
onError: (err, newUser, context) => {
// Rollback on error
queryClient.setQueryData(
userKeys.detail(newUser.id),
context?.previousUser
)
},
onSettled: (data, error, variables) => {
// Refetch after mutation
queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) })
},
})
}Pattern 5: Combining Client + Server State
// Zustand for client state
const useUIStore = create<UIState>((set) => ({
sidebarOpen: true,
modal: null,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
openModal: (modal) => set({ modal }),
closeModal: () => set({ modal: null }),
}))
// React Query for server state
function Dashboard() {
const { sidebarOpen, toggleSidebar } = useUIStore()
const { data: users, isLoading } = useUsers({ active: true })
const { data: stats } = useStats()
if (isLoading) return <DashboardSkeleton />
return (
<div className={sidebarOpen ? 'with-sidebar' : ''}>
<Sidebar open={sidebarOpen} onToggle={toggleSidebar} />
<main>
<StatsCards stats={stats} />
<UserTable users={users} />
</main>
</div>
)
}Best Practices
Do's
- Colocate state - Keep state as close to where it's used as possible
- Use selectors - Prevent unnecessary re-renders with selective subscriptions
- Normalize data - Flatten nested structures for easier updates
- Type everything - Full TypeScript coverage prevents runtime errors
- Separate concerns - Server state (React Query) vs client state (Zustand)
Don'ts
- Don't over-globalize - Not everything needs to be in global state
- Don't duplicate server state - Let React Query manage it
- Don't mutate directly - Always use immutable updates
- Don't store derived data - Compute it instead
- Don't mix paradigms - Pick one primary solution per category
Migration Guides
From Legacy Redux to RTK
// Before (legacy Redux)
const ADD_TODO = 'ADD_TODO'
const addTodo = (text) => ({ type: ADD_TODO, payload: text })
function todosReducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, { text: action.payload, completed: false }]
default:
return state
}
}
// After (Redux Toolkit)
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
// Immer allows "mutations"
state.push({ text: action.payload, completed: false })
},
},
})Resources
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-02-24T22:25:54.016Z",
"slug": "sickn33-react-state-management",
"source_url": "https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/react-state-management",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "6fe36370399452244ae0564573ca5cb6c920869e928a8687e34e1e3ba6674faa",
"tree_hash": "42cbb72f72cb3f1523c1a360956b5cf35057be977506d593d2407f3a57b723ed"
},
"skill": {
"name": "react-state-management",
"description": "Master modern React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when setting up global state, managing server state, or choosing between state management solutions.",
"summary": "Master modern React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when s...",
"category": "coding",
"icon": "📦",
"version": "1.0.1",
"author": "sickn33",
"license": "MIT",
"tags": [
"react",
"redux",
"zustand",
"state-management",
"react-query"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": [
"external_commands",
"network"
]
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "This is a documentation skill for React state management patterns. Static analysis flagged external_commands, network, and sensitive data patterns, but evaluation confirms these are FALSE POSITIVES - the 'backticks' are TypeScript template literals in code examples, 'fetch' calls are legitimate API examples, and 'localStorage' access is proper state persistence teaching. No actual security risks identified.",
"risk_factor_evidence": [
{
"factor": "external_commands",
"evidence": [
{
"file": "SKILL.md",
"line_start": 22,
"line_end": 22
},
{
"file": "SKILL.md",
"line_start": 47,
"line_end": 52
},
{
"file": "SKILL.md",
"line_start": 52,
"line_end": 58
},
{
"file": "SKILL.md",
"line_start": 58,
"line_end": 96
},
{
"file": "SKILL.md",
"line_start": 96,
"line_end": 102
},
{
"file": "SKILL.md",
"line_start": 102,
"line_end": 128
},
{
"file": "SKILL.md",
"line_start": 128,
"line_end": 130
},
{
"file": "SKILL.md",
"line_start": 130,
"line_end": 156
},
{
"file": "SKILL.md",
"line_start": 156,
"line_end": 197
},
{
"file": "SKILL.md",
"line_start": 197,
"line_end": 201
},
{
"file": "SKILL.md",
"line_start": 201,
"line_end": 246
},
{
"file": "SKILL.md",
"line_start": 246,
"line_end": 250
},
{
"file": "SKILL.md",
"line_start": 250,
"line_end": 268
},
{
"file": "SKILL.md",
"line_start": 268,
"line_end": 291
},
{
"file": "SKILL.md",
"line_start": 291,
"line_end": 295
},
{
"file": "SKILL.md",
"line_start": 295,
"line_end": 358
},
{
"file": "SKILL.md",
"line_start": 358,
"line_end": 362
},
{
"file": "SKILL.md",
"line_start": 362,
"line_end": 390
},
{
"file": "SKILL.md",
"line_start": 390,
"line_end": 412
},
{
"file": "SKILL.md",
"line_start": 412,
"line_end": 436
}
]
},
{
"factor": "network",
"evidence": [
{
"file": "SKILL.md",
"line_start": 156,
"line_end": 156
},
{
"file": "SKILL.md",
"line_start": 268,
"line_end": 268
},
{
"file": "SKILL.md",
"line_start": 440,
"line_end": 440
},
{
"file": "SKILL.md",
"line_start": 441,
"line_end": 441
},
{
"file": "SKILL.md",
"line_start": 442,
"line_end": 442
},
{
"file": "SKILL.md",
"line_start": 443,
"line_end": 443
}
]
}
],
"critical_findings": [],
"high_findings": [],
"medium_findings": [
{
"title": "External Command Pattern Detected (FALSE POSITIVE)",
"description": "Static analyzer flagged backtick usage as shell command execution. This is a false positive - backticks are TypeScript template literals in code examples for educational content about React state management.",
"locations": [
{
"file": "SKILL.md",
"line_start": 22,
"line_end": 22
},
{
"file": "SKILL.md",
"line_start": 47,
"line_end": 52
},
{
"file": "SKILL.md",
"line_start": 156,
"line_end": 156
}
]
},
{
"title": "Network API Calls in Code Examples (FALSE POSITIVE)",
"description": "Static analyzer flagged fetch() calls and URLs. These are legitimate code examples teaching proper API integration patterns.",
"locations": [
{
"file": "SKILL.md",
"line_start": 156,
"line_end": 156
},
{
"file": "SKILL.md",
"line_start": 268,
"line_end": 268
},
{
"file": "SKILL.md",
"line_start": 440,
"line_end": 443
}
]
}
],
"low_findings": [
{
"title": "Browser Storage Access (FALSE POSITIVE)",
"description": "localStorage usage flagged as sensitive data access. This is proper state persistence teaching in React patterns.",
"locations": [
{
"file": "SKILL.md",
"line_start": 261,
"line_end": 261
},
{
"file": "SKILL.md",
"line_start": 276,
"line_end": 276
}
]
}
],
"dangerous_patterns": [],
"files_scanned": 1,
"total_lines": 444,
"audit_model": "claude",
"audited_at": "2026-02-24T22:25:54.016Z"
},
"content": {
"user_title": "Implement React State Management",
"value_statement": "This skill helps developers choose and implement the right state management solution for React applications, from simple local state to complex global stores with server synchronization.",
"seo_keywords": [
"React state management",
"Redux Toolkit tutorial",
"Zustand React",
"Jotai atomic state",
"React Query TanStack",
"Claude Code React",
"Codex React patterns",
"React global state",
"React server state",
"React hooks state"
],
"actual_capabilities": [
"Guide selection between Redux Toolkit, Zustand, Jotai based on app complexity",
"Implement TypeScript-typed state management with proper typing patterns",
"Set up React Query for server state caching and synchronization",
"Create atomic state patterns with Jotai for granular updates",
"Implement optimistic updates and data persistence strategies",
"Debug state issues and migrate from legacy Redux patterns"
],
"limitations": [
"Does not execute code - provides guidance and code examples only",
"Cannot access external projects without file paths provided",
"Cannot debug runtime issues in live applications",
"Does not install dependencies or modify package.json"
],
"use_cases": [
{
"title": "New React Project Setup",
"description": "Choose and configure the appropriate state management solution when starting a new React application",
"target_user": "Frontend developers starting new React projects"
},
{
"title": "Legacy Migration",
"description": "Migrate from older Redux patterns or class components to modern state management approaches",
"target_user": "Developers maintaining older React codebases"
},
{
"title": "Performance Optimization",
"description": "Reduce unnecessary re-renders and optimize state access patterns in existing applications",
"target_user": "React developers looking to improve app performance"
}
],
"prompt_templates": [
{
"title": "Basic State Setup",
"prompt": "Show me how to set up Zustand for a simple React app with user authentication state and theme toggle.",
"scenario": "Setting up a new project with basic global state"
},
{
"title": "Redux Toolkit Setup",
"prompt": "Create a Redux Toolkit store with typed hooks and a user slice that handles async fetching.",
"scenario": "Implementing Redux Toolkit with TypeScript"
},
{
"title": "Server State with React Query",
"prompt": "Set up React Query for fetching user data with caching, optimistic updates on mutations, and error handling.",
"scenario": "Managing server state and API synchronization"
},
{
"title": "Atomic State Pattern",
"prompt": "Implement Jotai atoms for a shopping cart with derived atoms for totals and persistence to localStorage.",
"scenario": "Using atomic state for granular reactivity"
}
],
"output_examples": [
{
"input": "How do I choose between Redux Toolkit and Zustand?",
"output": "For small to medium apps with simple state needs, Zustand offers a minimal API with excellent TypeScript support. For large enterprise apps requiring middleware, devtools integration, and strict patterns, Redux Toolkit provides structure. Use Zustand when: you need simple global state without boilerplate, you prefer hooks-based API, you want minimal bundle size. Use Redux Toolkit when: you need complex async logic with thunks, you require strict action tracking for debugging, your team is familiar with Redux patterns."
},
{
"input": "Set up React Query for data fetching",
"output": "Install @tanstack/react-query, then wrap your app with QueryClientProvider. Create a query client with default options for caching. Use useQuery hook for fetching with keys for cache management. Use useMutation for updates with optimistic updates via onMutate. The query key ['users', userId] enables automatic refetching and caching."
}
],
"best_practices": [
"Separate client state from server state - use React Query for server data and Zustand/Redux for UI state",
"Colocate state close to where it is used - avoid putting everything in global state",
"Use TypeScript for all state-related code to prevent runtime errors and enable better IDE support"
],
"anti_patterns": [
"Storing server data in global state instead of using React Query - creates synchronization issues",
"Over-globalizing state - putting component-specific state in Redux/Zustand when useState suffices",
"Mutating state directly instead of using immutable update patterns - breaks reactivity"
],
"faq": [
{
"question": "What is the best state management for a new React project?",
"answer": "For most new projects, Zustand offers the best balance of simplicity and power. Use React Query for server state. Only reach for Redux Toolkit when you have complex async workflows or need middleware."
},
{
"question": "When should I use React Query vs Redux?",
"answer": "Use React Query for server state (API data, caching, synchronization). Use Redux or Zustand for client state (UI state, user sessions, form data). Never duplicate server state in global stores."
},
{
"question": "Can I use multiple state solutions together?",
"answer": "Yes - this is recommended. Use React Query for server state, Zustand or Redux for client state, and useState for local component state. Each serves a different purpose."
},
{
"question": "How do I persist state across page reloads?",
"answer": "Use Zustand persist middleware, Jotai atomWithStorage, or Redux persist. For sensitive data, consider sessionStorage instead of localStorage."
},
{
"question": "What is the difference between Jotai and Zustand?",
"answer": "Jotai uses atomic state where each atom is independent and derived atoms recompute automatically. Zustand uses a single store with selectors. Jotai excels at granular updates, Zustand at simplicity."
},
{
"question": "How do I handle async actions in Redux Toolkit?",
"answer": "Use createAsyncThunk for async operations. It handles pending/fulfilled/rejected states automatically and integrates with Redux DevTools for debugging."
}
]
},
"file_structure": [
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 444
}
]
}
Related skills
FAQ
Which library should I pick?
Zustand or Jotai for small apps, Redux Toolkit for large complex state, and React Query for heavy server interaction.
Does it cover server state?
Yes, it covers React Query, SWR, and RTK Query for remote data and caching.