Appearance
Zustand (Pinia vibes, less ceremony)
I've decided Zustand is the store I'd reach for first in a React SPA. Feels closer to Pinia than Redux ever did.
Install
bash
npm install zustandA tiny store
ts
// stores/useCartStore.ts
import { create } from 'zustand'
type CartItem = { id: string; name: string; qty: number }
type CartState = {
items: CartItem[]
add: (item: CartItem) => void
remove: (id: string) => void
clear: () => void
}
export const useCartStore = create<CartState>((set) => ({
items: [],
add: (item) =>
set((state) => ({ items: [...state.items, item] })),
remove: (id) =>
set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
clear: () => set({ items: [] }),
}))Comments for the code above:
- One
create()call - store + actions together (PiniadefineStoreenergy) setmerges / replaces - keep updates immutable- No Provider required for the basic case (nice)
Using it in a component
tsx
function CartBadge() {
const items = useCartStore((s) => s.items)
return <span>{items.length}</span>
}
function AddButton() {
const add = useCartStore((s) => s.add)
return (
<button
type="button"
onClick={() => add({ id: '1', name: 'Sticker', qty: 1 })}
>
Add
</button>
)
}As you can see - you pass a selector. Component re-renders when the selected slice changes. Select narrowly (s.items.length vs whole s) to avoid extra renders.
Pinia → Zustand map
| Pinia | Zustand |
|---|---|
defineStore('id', { state, actions, getters }) | create((set, get) => ({ … })) |
storeToRefs / computed getters | select in the hook, or derive outside |
| plugins | middleware (persist, devtools) |
| always works inside setup | works in components via the hook; getState() outside React too |
Persist middleware (localStorage)
ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useSettingsStore = create(
persist(
(set) => ({
density: 'cozy' as 'cozy' | 'compact',
setDensity: (density: 'cozy' | 'compact') => set({ density }),
}),
{ name: 'settings' },
),
)What I put in Zustand vs Query
- UI flags, wizard steps, client-only preferences → Zustand
- API lists / details / mutations with cache → TanStack Query
- Auth token sometimes lives in Zustand (or httpOnly cookies + server) - depends on the app
Next: TanStack Query - the library that made me stop writing fetch-in-Pinia clones.
Enjoy!