All topics
Frontend · Learning hub

Zustand notes for developers

Master Zustand with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Zustand

Zustand State Management

Zustand State Management Basic Store import { create } from 'zustand'; // Simple store interface CounterStore { count: number; increment: () => void; decrement:

Zustand State Management

Basic Store

import { create } from 'zustand';

// Simple store
interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
  incrementBy: (amount: number) => void;
}

export const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  incrementBy: (amount) => set((state) => ({ count: state.count + amount })),
}));

// Usage in component
function Counter() {
  const count = useCounterStore((state) => state.count);
  const { increment, decrement, reset } = useCounterStore();
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

// Select multiple — avoid selecting whole store (causes re-render on any change)
const { count, increment } = useCounterStore(
  useShallow((state) => ({ count: state.count, increment: state.increment }))
);

Advanced Patterns

import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

// With Immer (mutable style updates) + devtools + persist
interface UserStore {
  users: User[];
  selectedId: string | null;
  addUser: (user: User) => void;
  removeUser: (id: string) => void;
  updateUser: (id: string, data: Partial<User>) => void;
  selectUser: (id: string) => void;
}

export const useUserStore = create<UserStore>()(
  devtools(
    persist(
      immer((set) => ({
        users: [],
        selectedId: null,

        addUser: (user) =>
          set((state) => { state.users.push(user); }),

        removeUser: (id) =>
          set((state) => {
            state.users = state.users.filter((u) => u.id !== id);
          }),

        updateUser: (id, data) =>
          set((state) => {
            const user = state.users.find((u) => u.id === id);
            if (user) Object.assign(user, data);
          }),

        selectUser: (id) =>
          set((state) => { state.selectedId = id; }),
      })),
      {
        name: 'user-store',              // localStorage key
        partialize: (state) => ({ selectedId: state.selectedId }),  // only persist selectedId
      }
    ),
    { name: 'UserStore' }               // devtools name
  )
);

// Subscribe outside React (e.g., analytics, logging)
useUserStore.subscribe(
  (state) => state.users.length,
  (count) => analytics.track('users_count', { count }),
  { fireImmediately: true }
);

// Reset store (useful for logout)
const initialState = { users: [], selectedId: null };
useUserStore.setState(initialState);

Async Actions & Slices

// Async actions — just use async functions
interface PostStore {
  posts: Post[];
  loading: boolean;
  error: string | null;
  fetchPosts: () => Promise<void>;
  createPost: (data: CreatePostInput) => Promise<void>;
}

export const usePostStore = create<PostStore>((set) => ({
  posts: [],
  loading: false,
  error: null,

  fetchPosts: async () => {
    set({ loading: true, error: null });
    try {
      const posts = await api.get('/posts');
      set({ posts, loading: false });
    } catch (err) {
      set({ error: 'Failed to fetch posts', loading: false });
    }
  },

  createPost: async (data) => {
    const post = await api.post('/posts', data);
    set((state) => ({ posts: [post, ...state.posts] }));
  },
}));

// Slice pattern — combine multiple slices
const createCartSlice = (set, get) => ({
  cart: [],
  addToCart: (item) => set((s) => ({ cart: [...s.cart, item] })),
  total: () => get().cart.reduce((sum, i) => sum + i.price, 0),
});

const createWishlistSlice = (set) => ({
  wishlist: [],
  addToWishlist: (item) => set((s) => ({ wishlist: [...s.wishlist, item] })),
});

export const useStore = create((...args) => ({
  ...createCartSlice(...args),
  ...createWishlistSlice(...args),
}));

Keep your Zustand knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever