Setting Up State Management (Zustand) for React Application
Zustand is a minimalist state management library for React. No boilerplate, no providers, no context. A store is a hook that works outside the component tree and doesn't cause unnecessary re-renders.
Suitable for most medium-sized SPAs where Redux is overkill and the built-in useState is insufficient.
What's Involved
Installing and configuring Zustand for a specific project: store structure, TypeScript typing, middleware, devtools integration, persistence, splitting into slices when needed.
Installation
npm install zustand
# or
pnpm add zustand
No peer dependencies. Package size — about 1 KB gzipped.
Basic Store
import { create } from 'zustand'
interface CartState {
items: CartItem[]
total: number
addItem: (item: CartItem) => void
removeItem: (id: string) => void
clearCart: () => void
}
export const useCartStore = create<CartState>((set, get) => ({
items: [],
total: 0,
addItem: (item) =>
set((state) => {
const items = [...state.items, item]
return { items, total: items.reduce((sum, i) => sum + i.price, 0) }
}),
removeItem: (id) =>
set((state) => {
const items = state.items.filter((i) => i.id !== id)
return { items, total: items.reduce((sum, i) => sum + i.price, 0) }
}),
clearCart: () => set({ items: [], total: 0 }),
}))
Usage in a component — just a hook:
function CartButton() {
const { items, addItem } = useCartStore()
// Component re-renders only if items or addItem changed
return <button onClick={() => addItem(product)}>Add to cart ({items.length})</button>
}
Selectors and Re-render Optimization
By default, a component subscribes to the entire store. To limit the subscription — pass a selector:
// Re-render only when total changes
const total = useCartStore((state) => state.total)
// Multiple values — via shallow comparison
import { useShallow } from 'zustand/react/shallow'
const { items, clearCart } = useCartStore(
useShallow((state) => ({ items: state.items, clearCart: state.clearCart }))
)
Middleware: devtools + persist
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }, false, 'auth/login'),
logout: () => set({ user: null, token: null }, false, 'auth/logout'),
}),
{
name: 'auth-storage',
// Save only token, not the whole store
partialize: (state) => ({ token: state.token }),
}
),
{ name: 'auth' }
)
)
Timeline
Basic Zustand setup for a project — 1–2 hours. Integration with React Router and Tanstack Query — 2–3 hours. Full store architecture for a large application — 1 day.







