How to use Zustand in React
Zustand is a minimal, hook-based state management library for React that provides global state without Redux’s actions, reducers, and providers — just a store and a hook.
As the creator of CoreUI with 25 years of front-end development experience, I use Zustand when an application needs shared state across many components but the full Redux setup would be overkill.
Zustand stores are plain JavaScript objects with state and actions defined together, and any component can subscribe to exactly the slice of state it needs.
The setup is a single create() call — no Provider, no boilerplate.
Create a store with state and actions.
// store/useCartStore.js
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useCartStore = create(
persist(
(set, get) => ({
// State
items: [],
total: 0,
// Actions
addItem: (product) => {
const items = get().items
const existing = items.find(i => i.id === product.id)
const updatedItems = existing
? items.map(i => i.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
)
: [...items, { ...product, quantity: 1 }]
const total = updatedItems.reduce((sum, i) => sum + i.price * i.quantity, 0)
set({ items: updatedItems, total })
},
removeItem: (id) => {
const items = get().items.filter(i => i.id !== id)
const total = items.reduce((sum, i) => sum + i.price * i.quantity, 0)
set({ items, total })
},
clearCart: () => set({ items: [], total: 0 })
}),
{
name: 'cart-storage' // persists to localStorage automatically
}
)
)
create() returns a hook. set updates the store, get reads current state. The persist middleware serializes state to localStorage — the cart survives page refreshes with no extra code.
Using the Store in Components
Subscribe to exactly the state each component needs.
// CartIcon.jsx - only subscribes to item count
import { useCartStore } from './store/useCartStore'
export function CartIcon() {
const itemCount = useCartStore(state =>
state.items.reduce((sum, i) => sum + i.quantity, 0)
)
return <span>🛒 {itemCount}</span>
}
// ProductCard.jsx - only subscribes to addItem action
import { useCartStore } from './store/useCartStore'
export function ProductCard({ product }) {
const addItem = useCartStore(state => state.addItem)
return (
<div>
<h3>{product.name}</h3>
<button onClick={() => addItem(product)}>Add to Cart</button>
</div>
)
}
// CartSummary.jsx - subscribes to items and total
import { useCartStore } from './store/useCartStore'
export function CartSummary() {
const { items, total, removeItem } = useCartStore()
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.name} × {item.quantity} = ${(item.price * item.quantity).toFixed(2)}
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<strong>Total: ${total.toFixed(2)}</strong>
</div>
)
}
Passing a selector to useCartStore(state => state.addItem) subscribes only to that property. CartIcon re-renders only when item count changes; ProductCard never re-renders on cart changes because it only subscribes to the addItem action (which never changes).
Best Practice Note
This is the same lightweight state pattern we recommend in CoreUI React templates for shopping cart, theme, and user preference state. No Provider wrapper is needed — Zustand stores are global singletons that any component can import and use directly. For server state (API data), use RTK Query or TanStack Query alongside Zustand rather than storing API responses in Zustand. See how to use Redux in React when you need DevTools, middleware, or the full Redux ecosystem.



