DEV Community

Alex Spinov
Alex Spinov

Posted on

Zustand Has a Free API You Should Know About

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>
}
Enter fullscreen mode Exit fullscreen mode

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 }))
)
Enter fullscreen mode Exit fullscreen mode

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
  )
)
Enter fullscreen mode Exit fullscreen mode

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 })
Enter fullscreen mode Exit fullscreen mode

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)
}))
Enter fullscreen mode Exit fullscreen mode

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)