Back to Curriculum

Advanced State Management with Zustand

📚 Lesson 17 of 18 ⏱️ 55 min

Advanced State Management with Zustand

55 min

Zustand is a small, fast, and scalable state management solution that provides a simple API without boilerplate.

It eliminates the need for providers, reducers, or complex setup while maintaining excellent TypeScript support.

Zustand is perfect for applications that need lightweight state management without the complexity of Redux.

Unlike Redux, Zustand doesn't require actions, reducers, or providers. You create stores directly and use them in components with hooks.

Zustand supports middleware like persist for localStorage persistence, immer for immutable updates, and devtools for debugging.

The library is tiny (less than 1KB) and has zero dependencies, making it an excellent choice for projects that want state management without the overhead.

Key Concepts

  • Zustand provides simple, boilerplate-free state management.
  • Stores are created with the create() function and used as hooks.
  • No providers or context needed - stores are accessed directly.
  • Middleware like persist enables localStorage persistence.
  • Excellent TypeScript support with minimal setup.

Learning Objectives

Master

  • Creating Zustand stores with the create() function
  • Using stores as hooks in React components
  • Implementing persistence with Zustand middleware
  • Organizing multiple stores for different concerns

Develop

  • State management architecture thinking
  • Understanding when Zustand is appropriate vs other solutions
  • Designing store structures for scalability

Tips

  • Split stores by domain (user, cart, theme) rather than creating one large store.
  • Use the persist middleware for state that should survive page refreshes.
  • Use immer middleware if you prefer mutable-style updates.
  • Enable devtools middleware in development for debugging.

Common Pitfalls

  • Creating stores that are too large - split by concern.
  • Not using TypeScript types for better type safety.
  • Overusing Zustand when React state would suffice.
  • Not considering persistence needs when designing stores.

Summary

  • Zustand provides simple, boilerplate-free state management.
  • Stores are created with create() and used as hooks.
  • Middleware extends functionality (persist, immer, devtools).
  • Perfect for lightweight state management needs.

Exercise

Create a comprehensive state management system using Zustand with multiple stores and persistence.

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

// User store
const useUserStore = create(
  persist(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      login: (userData) => set({ user: userData, isAuthenticated: true }),
      logout: () => set({ user: null, isAuthenticated: false }),
      updateProfile: (updates) => set((state) => ({
        user: { ...state.user, ...updates }
      }))
    }),
    {
      name: 'user-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
);

// Cart store
const useCartStore = create(
  persist(
    (set, get) => ({
      items: [],
      total: 0,
      addItem: (item) => set((state) => {
        const existingItem = state.items.find(i => i.id === item.id);
        if (existingItem) {
          return {
            items: state.items.map(i => 
              i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
            )
          };
        }
        return { items: [...state.items, { ...item, quantity: 1 }] };
      }),
      removeItem: (itemId) => set((state) => ({
        items: state.items.filter(i => i.id !== itemId)
      })),
      updateQuantity: (itemId, quantity) => set((state) => ({
        items: state.items.map(i => 
          i.id === itemId ? { ...i, quantity: Math.max(0, quantity) } : i
        )
      })),
      clearCart: () => set({ items: [], total: 0 }),
      getTotal: () => {
        const state = get();
        return state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
      }
    }),
    {
      name: 'cart-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
);

// Theme store
const useThemeStore = create(
  persist(
    (set) => ({
      theme: 'light',
      toggleTheme: () => set((state) => ({
        theme: state.theme === 'light' ? 'dark' : 'light'
      })),
      setTheme: (theme) => set({ theme })
    }),
    {
      name: 'theme-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
);

// Usage example
function App() {
  const { user, login, logout } = useUserStore();
  const { items, addItem, removeItem, getTotal } = useCartStore();
  const { theme, toggleTheme } = useThemeStore();

  return (
    <div className={`app ${theme}`}>
      <header>
        <h1>My App</h1>
        <button onClick={toggleTheme}>Toggle Theme</button>
        {user ? (
          <div>
            <span>Welcome, {user.name}!</span>
            <button onClick={logout}>Logout</button>
          </div>
        ) : (
          <button onClick={() => login({ id: 1, name: 'John Doe' })}>Login</button>
        )}
      </header>
      
      <main>
        <div className="cart">
          <h2>Cart ({items.length} items)</h2>
          <p>Total: ${getTotal()}</p>
          {items.map(item => (
            <div key={item.id}>
              {item.name} x {item.quantity}
              <button onClick={() => removeItem(item.id)}>Remove</button>
            </div>
          ))}
        </div>
      </main>
    </div>
  );
}

Code Editor

Output