Zustand is a simple and powerful state management library for React applications.
Unlike Redux, it has no heavy boilerplate, no reducers, no actions, and no complicated setup.
You just create a store using the create function and start using it immediately.
Here’s a basic todo store:
import { create } from 'zustand';
const useTodoStore = create(
(set) => ({
todos: [
{
id: crypto.randomUUID(),
text: 'Learn React',
completed: false,
},
{
id: crypto.randomUUID(),
text: 'Learn Zustand',
completed: false,
},
],
})
);
export default useTodoStore;
Consuming the Store in a Component
const todos = useTodoStore((state) => state.todos);
Need to add a new todo ?
To insert a new todo item into the store, you create an addTodo function inside your Zustand store. This function receives the new todo text and then updates the state using Zustand’s set function.
Zustand encourages immutable updates, meaning you should never directly mutate the previous state. You can also use immer for immutable updates easily. Instead, you create a new array by spreading the existing todos and adding the new one at the end.
import { create } from 'zustand';
const useTodoStore = create(
(set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: crypto.randomUUID(), text, completed: false },
],
})),
})
);
export default useTodoStore;
Use the addTodo function in a component
const addTodo = useTodoStore((state) => state.addTodo);
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
addTodo(text.trim());
setText('');
}
};
Persistance
You can persist the data in the local storage using the persist middleware that comes from zustand/middleware, like this
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useTodoStore = create(
persist(
(set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: crypto.randomUUID(), text, completed: false },
],
})),
})
)
);
export default useTodoStore;
By default, the persist middleware does not assign a storage key.
To customize it, you can pass a second configuration object to persist and set a name property, which becomes the key used in localStorage.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useTodoStore = create(
persist(
(set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: crypto.randomUUID(), text, completed: false },
],
})),
}),
{
name: 'todo-storage',
}
)
);
export default useTodoStore;
Devtools
Zustand also supports Redux DevTools using the devtools middleware.
This lets you inspect state changes, time travel, and debug more easily.
The set function can accept three arguments:
A function that returns the updated state
A boolean indicating whether to replace the entire state
A string that names the action (useful for DevTools)
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useTodoStore = create(
devtools(
persist(
(set) => ({
todos: [],
filter: 'all',
addTodo: (text) =>
set(
(state) => ({
todos: [
...state.todos,
{ id: crypto.randomUUID(), text, completed: false },
],
}),
false, // replace state?
"addTodo" // action name for DevTools
),
}),
{ name: 'todo-storage' }
),
{ name: "TodoStore" }
)
);
export default useTodoStore;
Now you can debug with the Redux devtool.
Summary
Zustand is a minimal state manager without boilerplate.
You create a store using create.
State consumption is simple: useStore((state) => state.value).
You can add actions like addTodo that modify state safely.
-
Persist middleware stores your data in localStorage.
- DevTools middleware integrates with Redux DevTools for debugging.
Zustand is perfect for React developers who want clean, predictable, and easy-to-maintain global state with minimal setup.
GitHub Repository & Live Demo
GitHub Repo:
👉 todo-app-zustand
Live Demo:
👉 Live Link




Top comments (0)