Zustand Tutorial

Tutorial Overview

Welcome to the comprehensive Zustand tutorial! This guide will take you from beginner to advanced, teaching you everything you need to know about state management with Zustand.

Lesson 1: Introduction to Zustand

Zustand is a lightweight state management library for React that provides:

  • Simple and intuitive API
  • No boilerplate code required
  • Great TypeScript support
  • Excellent performance with automatic optimization
  • Small bundle size (less than 1KB)

Why Choose Zustand?

Compared to other state management solutions like Redux or MobX, Zustand offers a simpler API with less boilerplate while maintaining powerful features and excellent performance.

Lesson 2: Creating Your First Store

Let's create a simple todo list application:

import { create } from 'zustand'

interface Todo {
  id: number
  text: string
  completed: boolean
}

interface TodoStore {
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: number) => void
  removeTodo: (id: number) => void
}

const useTodoStore = create<TodoStore>((set) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => ({
      todos: [
        ...state.todos,
        { id: Date.now(), text, completed: false }
      ]
    })),
  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    })),
  removeTodo: (id) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id)
    })),
}))

Lesson 3: Using the Store in Components

function TodoList() {
  const todos = useTodoStore((state) => state.todos)
  const addTodo = useTodoStore((state) => state.addTodo)
  const toggleTodo = useTodoStore((state) => state.toggleTodo)
  const removeTodo = useTodoStore((state) => state.removeTodo)
  const [input, setInput] = useState('')

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (input.trim()) {
      addTodo(input)
      setInput('')
    }
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a todo..."
        />
        <button type="submit">Add</button>
      </form>
      
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ 
              textDecoration: todo.completed ? 'line-through' : 'none' 
            }}>
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  )
}

Lesson 4: Async Actions & Advanced Patterns

Async Actions

Handling async actions in Zustand is simple - just make your action async!

const useStore = create((set) => ({
  data: null,
  loading: false,
  error: null,
  fetchData: async () => {
    set({ loading: true, error: null })
    try {
      const response = await fetch('https://api.example.com/data')
      const data = await response.json()
      set({ data, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  }
}))

Computed Values

Create derived state with selectors:

const useStore = create((set) => ({
  todos: [],
  // ... actions
}))

// In your component
const completedCount = useStore(
  (state) => state.todos.filter(t => t.completed).length
)
const totalCount = useStore((state) => state.todos.length)

Next Steps

Continue learning with these topics:

  • Middleware for persistence and devtools
  • Testing Zustand stores
  • Advanced TypeScript patterns
  • Performance optimization techniques
  • Integration with other libraries