Zustand is the most popular React state management library — minimal API, no boilerplate, no providers. Its middleware system and selectors make it incredibly powerful.
Basic Store
import { create } from "zustand"
interface BearStore {
bears: number
fish: number
addBear: () => void
addFish: (count: number) => void
reset: () => void
}
const useBearStore = create<BearStore>((set, get) => ({
bears: 0,
fish: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
addFish: (count) => set({ fish: get().fish + count }),
reset: () => set({ bears: 0, fish: 0 })
}))
// Use in any component — no Provider needed!
function BearCounter() {
const bears = useBearStore((state) => state.bears)
const addBear = useBearStore((state) => state.addBear)
return <button onClick={addBear}>Bears: {bears}</button>
}
Selectors — Prevent Unnecessary Rerenders
// Only rerenders when bears changes
const bears = useBearStore((state) => state.bears)
// Derived selectors
const totalAnimals = useBearStore((state) => state.bears + state.fish)
// Shallow comparison for objects
import { useShallow } from "zustand/react/shallow"
const { bears, fish } = useBearStore(
useShallow((state) => ({ bears: state.bears, fish: state.fish }))
)
Middleware Stack
import { create } from "zustand"
import { devtools, persist, subscribeWithSelector } from "zustand/middleware"
import { immer } from "zustand/middleware/immer"
const useStore = create<Store>()(
devtools(
persist(
subscribeWithSelector(
immer((set) => ({
users: [],
addUser: (user) => set((state) => {
state.users.push(user) // Immer — mutate directly!
}),
removeUser: (id) => set((state) => {
state.users = state.users.filter(u => u.id !== id)
})
}))
),
{ name: "app-store" } // Persists to localStorage
),
{ name: "MyApp" } // DevTools label
)
)
Subscribe Outside React
// Subscribe to changes outside components
const unsub = useBearStore.subscribe(
(state) => state.bears,
(bears, prevBears) => {
console.log(`Bears changed: ${prevBears} → ${bears}`)
if (bears > 10) sendAlert("Too many bears!")
}
)
// Read state outside React
const currentBears = useBearStore.getState().bears
// Set state outside React
useBearStore.setState({ bears: 0 })
Slices Pattern — Large Stores
const createAuthSlice = (set) => ({
user: null,
token: null,
login: async (email, password) => {
const { user, token } = await api.login(email, password)
set({ user, token })
},
logout: () => set({ user: null, token: null })
})
const createCartSlice = (set, get) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
total: () => get().items.reduce((sum, i) => sum + i.price, 0)
})
const useStore = create((...a) => ({
...createAuthSlice(...a),
...createCartSlice(...a)
}))
Key Takeaways
- No Provider — use stores anywhere
- Selectors prevent unnecessary rerenders
- Middleware for persistence, devtools, immer
- Subscribe outside React for side effects
- Slices for organizing large stores
- Tiny — 1.1KB gzipped
Explore Zustand docs for the complete API.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)