| name | react-state-management |
| description | Complete React state management system. PROACTIVELY activate for: (1) Context API patterns and optimization, (2) Zustand store setup and usage, (3) Jotai atomic state, (4) TanStack Query (React Query) for server state, (5) SWR data fetching, (6) useState vs useReducer decisions, (7) State normalization, (8) Avoiding prop drilling. Provides: Store configuration, context optimization, server state caching, optimistic updates, infinite queries. Ensures scalable state architecture with proper tool selection. |
Quick Reference
| Library | Best For | Install |
|---|
| Context | Small apps, themes | Built-in |
| Zustand | Simple global state | npm i zustand |
| Jotai | Atomic/granular state | npm i jotai |
| TanStack Query | Server state/caching | npm i @tanstack/react-query |
| SWR | Data fetching | npm i swr |
| Scenario | Recommended |
|---|
| Simple local state | useState |
| Complex local state | useReducer |
| Shared state (small app) | Context + useReducer |
| Shared state (large app) | Zustand or Jotai |
| Server state | TanStack Query or SWR |
When to Use This Skill
Use for state management decisions:
- Choosing between state management solutions
- Setting up Zustand, Jotai, or Context stores
- Configuring TanStack Query for server state
- Implementing optimistic updates
- Normalizing complex state structures
- Avoiding unnecessary re-renders
For React hooks basics: see react-hooks-complete
React State Management
Built-in State Management
Component State with useState
'use client';
import { useState } from 'react';
function ShoppingCart() {
const [items, setItems] = useState<CartItem[]>([]);
const [isOpen, setIsOpen] = useState(false);
const addItem = (product: Product) => {
setItems((prev) => {
const existing = prev.find((item) => item.id === product.id);
if (existing) {
return prev.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { ...product, quantity: 1 }];
});
};
const total = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return (
<div>
<button = => setIsOpen(!isOpen)}>
Cart ({items.length}) - ${total.toFixed(2)}
{isOpen && }
);
}
Complex State with useReducer
'use client';
import { useReducer, Dispatch, createContext, useContext } from 'react';
interface CartState {
items: CartItem[];
isLoading: boolean;
error: string | null;
}
type CartAction =
| { type: 'ADD_ITEM'; payload: Product }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
| { type: 'CLEAR_CART' }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string };
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case : {
existing = state..(
item. === action..
);
(existing) {
{
...state,
: state..(
item. === action..
? { ...item, : item. + }
: item
),
};
}
{
...state,
: [...state., { ...action., : }],
};
}
:
{
...state,
: state..( item. !== action.),
};
:
{
...state,
: state..(
item. === action..
? { ...item, : action.. }
: item
),
};
:
{ ...state, : [] };
:
{ ...state, : action. };
:
{ ...state, : action. };
:
state;
}
}
= createContext<{
: ;
: <>;
} | >();
() {
[state, dispatch] = (cartReducer, {
: [],
: ,
: ,
});
(
);
}
() {
context = ();
(!context) {
();
}
context;
}
Context API
Creating and Using Context
import { createContext, useContext, useState, ReactNode } from 'react';
interface Theme {
colors: { primary: string; secondary: string; background: string };
spacing: { sm: number; md: number; lg: number };
}
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
toggleDarkMode: () => void;
isDark: boolean;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
const lightTheme: Theme = {
colors: { primary: '#3b82f6', secondary: '#8b5cf6', background: '#ffffff' },
spacing: { sm: 8, md: , : },
};
: = {
: { : , : , : },
: { : , : , : },
};
() {
[isDark, setIsDark] = ();
[theme, setTheme] = useState<>(lightTheme);
= () => {
( !prev);
(isDark ? lightTheme : darkTheme);
};
(
);
}
() {
context = ();
(!context) {
();
}
context;
}
Optimizing Context Performance
import { createContext, useContext, useMemo, useCallback, useState } from 'react';
const UserContext = createContext<User | null>(null);
const UserActionsContext = createContext<{
login: (email: string, password: string) => Promise<void>;
logout: () => void;
updateProfile: (data: Partial<User>) => Promise<void>;
} | null>(null);
export function UserProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = useCallback(async (email: string, password: string) => {
const response = await fetch('/api/login', {
: ,
: .({ email, password }),
});
userData = response.();
(userData);
}, []);
logout = ( {
();
}, []);
updateProfile = ( (: <>) => {
response = (, {
: ,
: .(data),
});
updated = response.();
(updated);
}, []);
actions = (
({ login, logout, updateProfile }),
[login, logout, updateProfile]
);
(
);
}
() {
();
}
() {
context = ();
(!context) {
();
}
context;
}
Zustand
Basic Zustand Store
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
interface CartStore {
items: CartItem[];
addItem: (product: Product) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
devtools(
persist(
(set, get) => ({
items: [],
addItem: (product) =>
set((state) => {
const existing = state.items.find((item) => item. === product.);
(existing) {
{
: state..(
item. === product.
? { ...item, : item. + }
: item
),
};
}
{ : [...state., { ...product, : }] };
}),
:
( ({
: state..( item. !== id),
})),
:
( ({
: state..(
item. === id ? { ...item, quantity } : item
),
})),
: ({ : [] }),
:
()..(
sum + item. * item.,
),
}),
{ : }
)
)
);
() {
items = ( state.);
total = ( state.());
(
);
}
Zustand with Immer
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface TodoStore {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
deleteTodo: (id: string) => void;
}
export const useTodoStore = create<TodoStore>()(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({
id: crypto.randomUUID(),
text,
completed: false,
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.( t. === id);
(todo) {
todo. = !todo.;
}
}),
:
( {
index = state..( t. === id);
(index !== -) {
state..(index, );
}
}),
}))
);
Jotai
Basic Jotai Atoms
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const countAtom = atom(0);
const textAtom = atom('');
const doubleCountAtom = atom((get) => get(countAtom) * 2);
const uppercaseTextAtom = atom(
(get) => get(textAtom).toUpperCase(),
(get, set, newValue: string) => set(textAtom, newValue.toLowerCase())
);
const userAtom = atom(async () => {
const response = await fetch('/api/user');
return response.json();
});
const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light');
() {
[count, setCount] = (countAtom);
doubleCount = (doubleCountAtom);
(
);
}
Jotai with Async Actions
import { atom, useAtom } from 'jotai';
import { atomWithQuery, atomWithMutation } from 'jotai-tanstack-query';
const postsAtom = atomWithQuery(() => ({
queryKey: ['posts'],
queryFn: async () => {
const res = await fetch('/api/posts');
return res.json();
},
}));
const createPostAtom = atomWithMutation(() => ({
mutationFn: async (newPost: { title: string; content: string }) => {
const res = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost),
});
return res.json();
},
}));
function Posts() {
const [{ data: posts, isLoading }] = useAtom(postsAtom);
const [{ mutate: createPost, isPending }] = (createPostAtom);
(isLoading) ;
(
);
}
TanStack Query (React Query)
Basic Queries
import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30,
retry: 3,
refetchOnWindowFocus: true,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<Posts />
</QueryClientProvider>
);
}
function Posts() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const res = await fetch('/api/posts');
if (!res.) ();
res.();
},
});
(isLoading) ;
(error) ;
(
);
}
Mutations with Optimistic Updates
function useCreatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newPost: CreatePostInput) => {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
if (!res.ok) throw new Error('Failed to create post');
return res.json();
},
onMutate: async (newPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] });
const previousPosts = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], (old: Post[]) => [
{ ...newPost, : , : () },
...old,
]);
{ previousPosts };
},
: {
queryClient.([], context?.);
},
: {
queryClient.({ : [] });
},
});
}
() {
createPost = ();
= () => {
e.();
formData = (e.);
createPost.({
: formData.() ,
: formData.() ,
});
};
(
);
}
Infinite Queries
import { useInfiniteQuery } from '@tanstack/react-query';
function InfinitePosts() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: async ({ pageParam = 0 }) => {
const res = await fetch(`/api/posts?cursor=${pageParam}&limit=10`);
return res.json();
},
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 0,
});
if (isLoading) return <Spinner />;
return (
<div>
{data?.pages.map((page, i) => (
<Fragment key={i}>
{page.posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'No more posts'}
);
}
SWR
Basic SWR Usage
import useSWR, { SWRConfig } from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function App() {
return (
<SWRConfig
value={{
fetcher,
refreshInterval: 0,
revalidateOnFocus: true,
dedupingInterval: 2000,
}}
>
<Dashboard />
</SWRConfig>
);
}
function Dashboard() {
const { data, error, isLoading, mutate } = useSWR('/api/dashboard');
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return (
<div>
Dashboard
Total Users: {data.totalUsers}
mutate()}>Refresh
);
}
SWR Mutation
import useSWRMutation from 'swr/mutation';
async function createUser(url: string, { arg }: { arg: CreateUserInput }) {
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(arg),
});
return res.json();
}
function CreateUserForm() {
const { trigger, isMutating } = useSWRMutation('/api/users', createUser);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await trigger({
name: formData.get('name') as string,
email: formData.get('email') as string,
});
};
return (
{isMutating ? 'Creating...' : 'Create'}
);
}
Best Practices
1. Choose the Right Tool
| Scenario | Recommended |
|---|
| Simple local state | useState |
| Complex local state | useReducer |
| Shared state (small app) | Context + useReducer |
| Shared state (large app) | Zustand or Jotai |
| Server state | TanStack Query or SWR |
2. Avoid Prop Drilling
<Parent user={user}>
<Child user={user}>
<GrandChild user={user} />
</Child>
</Parent>
<UserProvider>
<Parent>
<Child>
<GrandChild /> {/* Access user via useUser() */}
</Child>
</Parent>
</UserProvider>
3. Normalize Complex State
const badState = {
posts: [
{ id: 1, title: 'Post 1', author: { id: 1, name: 'Alice' } },
{ id: 2, title: 'Post 2', author: { id: 1, name: 'Alice' } },
],
};
const goodState = {
posts: {
byId: { 1: { id: 1, title: 'Post 1', authorId: 1 } },
allIds: [1, 2],
},
authors: {
byId: { 1: { id: 1, name: 'Alice' } },
allIds: [1],
},
};
Additional References
For detailed patterns and advanced use cases, see:
references/zustand-patterns.md - Advanced Zustand patterns including slices, middleware, and testing