Zustand

A Tiny Store

Zustand is a small (about 1 KB) state library popular in Next.js apps. No provider needed; components subscribe to just the slice they use.

Example

npm install zustand

Example output

added 1 package in 2s
// stores/cart.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

type CartItem = { productId: number; name: string; price: number; qty: number };
type CartState = {
  items: CartItem[];
  add: (item: Omit<CartItem, 'qty'>) => void;
  remove: (productId: number) => void;
};

export const useCartStore = create<CartState>()(
  persist(
    (set) => ({
      items: [],
      add: (item) =>
        set((s) => {
          const found = s.items.find((i) => i.productId === item.productId);
          return {
            items: found
              ? s.items.map((i) => (i.productId === item.productId ? { ...i, qty: i.qty + 1 } : i))
              : [...s.items, { ...item, qty: 1 }],
          };
        }),
      remove: (id) => set((s) => ({ items: s.items.filter((i) => i.productId !== id) })),
    }),
    { name: 'shopease-cart' }, // saved in localStorage
  ),
);
'use client';
import { useCartStore } from '@/stores/cart';

export function CartCount() {
  const count = useCartStore((s) => s.items.reduce((n, i) => n + i.qty, 0)); // re-renders only when count changes
  return <span>{count}</span>;
}

How a Store Works

Example

The core of Zustand in 20 lines: state, set, and subscribers that get notified:

type Listener<T> = (state: T, prev: T) => void;

function createStore<T>(init: (set: (fn: (s: T) => Partial<T>) => void) => T) {
  let state: T;
  const listeners = new Set<Listener<T>>();
  const set = (fn: (s: T) => Partial<T>) => {
    const prev = state;
    state = { ...state, ...fn(state) };
    listeners.forEach((l) => l(state, prev));
  };
  state = init(set);
  return { get: () => state, subscribe: (l: Listener<T>) => (listeners.add(l), () => listeners.delete(l)) };
}

type Cart = { items: string[]; add: (name: string) => void };
const store = createStore<Cart>((set) => ({
  items: [],
  add: (name) => set((s) => ({ items: [...s.items, name] })),
}));

const unsubscribe = store.subscribe((s, prev) => console.log('badge:', prev.items.length, '->', s.items.length));
store.get().add('Clean Code');
store.get().add('USB-C Cable');
unsubscribe();
store.get().add('Notebook');
console.log('items:', store.get().items.join(', '));

Output

badge: 0 -> 1
badge: 1 -> 2
items: Clean Code, USB-C Cable, Notebook

SSR Caution

Note: A module-level store is shared by all users on the server. Only read/write it in Client Components (or create one store per request through a provider). Never put user data in a global server variable.