Zustand: State Management Without Boilerplate

Zustand: State Management Without Boilerplate

Discover Zustand, a lightweight state management library for React. Build a TypeScript-based todo app and simplify your React development process.

Managing global state in React applications doesn’t have to be complicated. Zustand is a lightweight state management library that simplifies the process with a minimalistic API and powerful features.

In this blog post, we’ll explore how Zustand works with a practical example: a todo list application built in TypeScript.


What Is Zustand?

Zustand (German for "state") is a fast, flexible, and boilerplate-free state management library. Unlike Redux, Zustand avoids complexity, allowing you to focus on building features.

Key Features:

  • Minimal API: Define and access state with ease.
  • Built for React: Fully compatible with React's reactivity model.
  • TypeScript Ready: First-class TypeScript support for strongly typed stores.
  • Efficient Updates: Components only re-render when their specific slice of state changes.

Installation

To get started, install Zustand using your package manager:

npm install zustand
# or
yarn add zustand

Building a Todo List with Zustand

Let’s create a todo list application to see Zustand in action. We’ll handle adding, toggling, and removing todos, all managed in a single store.


Step 1: Create the Store

We’ll use Zustand’s create function to define our store, complete with state and actions.

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
}

export const useTodoStore = create<TodoStore>((set) => ({
  todos: [],

  addTodo: (text) => set((state) => ({
    todos: [ // Spread all, then add new todo
      ...state.todos, {
        id: Date.now(),
        text,
        completed: false
      }
    ]
  })),

  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? {
        // Spread all properties, then toggle completed
        ...todo,
        completed: !todo.completed
      } : todo
    )
  })),

  removeTodo: (id) => set((state) => ({
    todos: state.todos.filter((todo) =>
      // Filter out the todo with the matching ID
      todo.id !== id
    )
  }))
}))

Here’s what’s happening:

  1. todos is our list of todo items.
  2. addTodo adds a new todo with a unique ID.
  3. toggleTodo toggles the completed status of a todo by ID.
  4. removeTodo removes a todo by ID.

Step 2: Use the Store in Components

Now, let’s build components that interact with our store.

Displaying Todos

import React from "react"
import { useTodoStore } from "./todoStore"
import { FC } from "react"

const TodoList: FC = () => {
  // Access the todos and actions from the store
  const todos = useTodoStore((state) => state.todos)
  const toggleTodo = useTodoStore((state) => state.toggleTodo)
  const removeTodo = useTodoStore((state) => state.removeTodo)

  return <ul>
    {todos.map((todo) =>
      <li key={todo.id}>
        <span
          onClick={() => toggleTodo(todo.id)}
          style={{
            textDecoration: todo.completed
              ? "line-through"
              : "none",
            cursor: "pointer"
          }}
        >
          {todo.text}
        </span>

        <button onClick={() => removeTodo(todo.id)}>
          Remove
        </button>
      </li>
    )}
  </ul>
}

export default TodoList

Adding Todos

import React, { useState } from "react"
import { useTodoStore } from "./todoStore"
import { FC } from "react"

const AddTodo: FC = () => {
  const [text, setText] = useState("")
  const addTodo = useTodoStore((state) => state.addTodo)

  const handleAdd = () => {
    if (text.trim()) {
      addTodo(text)
      setText("")
    }
  }

  return <div>
    <input
      type="text"
      value={text}
      onChange={(event) => setText(event.target.value)}
      placeholder="Add a new todo"
    />

    <button onClick={handleAdd}>
      Add Todo
    </button>
  </div>
}

export default AddTodo

Step 3: Bringing It All Together

Finally, combine the TodoList and AddTodo components into a single app:

import React from "react"
import TodoList from "./TodoList"
import AddTodo from "./AddTodo"
import { FC } from "react"

const App: FC = () => {
  return <div>
    <h1>Todo List</h1>
    <AddTodo />
    <TodoList />
  </div>
}

export default App

Why Use Zustand?

Zustand stands out for its simplicity and power. Here’s why it’s worth considering:

  • No Boilerplate: Manage state and actions in one place.
  • Performance: Avoid unnecessary renders with selectors.
  • TypeScript Support: Strongly type your stores for better developer experience.
  • Scalable: Works for both small projects and complex apps.

Conclusion

Zustand is a fantastic choice for React developers who want simple yet powerful state management. Whether you’re working on a small project or scaling a larger application, Zustand offers the flexibility you need without the complexity.

To learn more, check out the Zustand GitHub Repository.


Ready to simplify your state management? Give Zustand a try!