Zustand
Zustand is a small, fast, and scalable state management solution. It's simpler than Redux but more powerful than Context.
Installation
bash
1npm install zustandBasic Usage
jsx
1import { create } from 'zustand'
2
3// Create a store
4const useStore = create((set) => ({
5 count: 0,
6 increment: () => set((state) => ({ count: state.count + 1 })),
7 decrement: () => set((state) => ({ count: state.count - 1 })),
8 reset: () => set({ count: 0 })
9}))
10
11// Use in components
12function Counter() {
13 const count = useStore((state) => state.count)
14 const increment = useStore((state) => state.increment)
15 const decrement = useStore((state) => state.decrement)
16
17 return (
18 <div>
19 <p>Count: {count}</p>
20 <button onClick={increment}>+</button>
21 <button onClick={decrement}>-</button>
22 </div>
23 )
24}Store with Multiple Slices
jsx
1const useStore = create((set, get) => ({
2 // User state
3 user: null,
4 login: (userData) => set({ user: userData }),
5 logout: () => set({ user: null }),
6
7 // Theme state
8 theme: 'light',
9 toggleTheme: () => set((state) => ({
10 theme: state.theme === 'light' ? 'dark' : 'light'
11 })),
12
13 // Cart state
14 items: [],
15 addItem: (item) => set((state) => ({
16 items: [...state.items, item]
17 })),
18 removeItem: (id) => set((state) => ({
19 items: state.items.filter(item => item.id !== id)
20 })),
21
22 // Computed values using get()
23 totalItems: () => get().items.length,
24 totalPrice: () => get().items.reduce((sum, item) => sum + item.price, 0)
25}))Selecting State
Only re-render when selected state changes:
jsx
1// ✅ Good - only re-renders when count changes
2function Counter() {
3 const count = useStore((state) => state.count)
4 return <p>{count}</p>
5}
6
7// ❌ Bad - re-renders on any state change
8function Counter() {
9 const state = useStore()
10 return <p>{state.count}</p>
11}
12
13// Multiple selections
14function UserInfo() {
15 const user = useStore((state) => state.user)
16 const theme = useStore((state) => state.theme)
17
18 return <div className={theme}>{user?.name}</div>
19}
20
21// Shallow comparison for objects
22import { shallow } from 'zustand/shallow'
23
24function Cart() {
25 const { items, totalPrice } = useStore(
26 (state) => ({ items: state.items, totalPrice: state.totalPrice() }),
27 shallow
28 )
29}Async Actions
jsx
1const useStore = create((set, get) => ({
2 users: [],
3 loading: false,
4 error: null,
5
6 fetchUsers: async () => {
7 set({ loading: true, error: null })
8
9 try {
10 const response = await fetch('/api/users')
11 const users = await response.json()
12 set({ users, loading: false })
13 } catch (error) {
14 set({ error: error.message, loading: false })
15 }
16 },
17
18 createUser: async (userData) => {
19 try {
20 const response = await fetch('/api/users', {
21 method: 'POST',
22 body: JSON.stringify(userData)
23 })
24 const newUser = await response.json()
25 set((state) => ({ users: [...state.users, newUser] }))
26 return newUser
27 } catch (error) {
28 set({ error: error.message })
29 throw error
30 }
31 }
32}))
33
34// Usage
35function UserList() {
36 const { users, loading, error, fetchUsers } = useStore()
37
38 useEffect(() => {
39 fetchUsers()
40 }, [])
41
42 if (loading) return <p>Loading...</p>
43 if (error) return <p>Error: {error}</p>
44
45 return (
46 <ul>
47 {users.map(user => (
48 <li key={user.id}>{user.name}</li>
49 ))}
50 </ul>
51 )
52}Persist Middleware
Save state to localStorage:
jsx
1import { create } from 'zustand'
2import { persist } from 'zustand/middleware'
3
4const useStore = create(
5 persist(
6 (set) => ({
7 theme: 'light',
8 toggleTheme: () => set((state) => ({
9 theme: state.theme === 'light' ? 'dark' : 'light'
10 }))
11 }),
12 {
13 name: 'app-storage', // localStorage key
14 partialize: (state) => ({ theme: state.theme }) // only persist theme
15 }
16 )
17)DevTools Middleware
jsx
1import { create } from 'zustand'
2import { devtools } from 'zustand/middleware'
3
4const useStore = create(
5 devtools(
6 (set) => ({
7 count: 0,
8 increment: () => set(
9 (state) => ({ count: state.count + 1 }),
10 false,
11 'increment' // action name for devtools
12 )
13 }),
14 { name: 'MyStore' }
15 )
16)Combine Multiple Middlewares
jsx
1import { create } from 'zustand'
2import { devtools, persist } from 'zustand/middleware'
3
4const useStore = create(
5 devtools(
6 persist(
7 (set) => ({
8 // state and actions
9 }),
10 { name: 'app-storage' }
11 ),
12 { name: 'MyStore' }
13 )
14)TypeScript Support
tsx
1interface User {
2 id: string
3 name: string
4 email: string
5}
6
7interface Store {
8 user: User | null
9 login: (user: User) => void
10 logout: () => void
11}
12
13const useStore = create<Store>((set) => ({
14 user: null,
15 login: (user) => set({ user }),
16 logout: () => set({ user: null })
17}))Complete Example: Todo Store
jsx
1import { create } from 'zustand'
2import { persist } from 'zustand/middleware'
3
4const useTodoStore = create(
5 persist(
6 (set, get) => ({
7 todos: [],
8 filter: 'all',
9
10 addTodo: (text) => set((state) => ({
11 todos: [...state.todos, {
12 id: Date.now(),
13 text,
14 completed: false,
15 createdAt: new Date()
16 }]
17 })),
18
19 toggleTodo: (id) => set((state) => ({
20 todos: state.todos.map(todo =>
21 todo.id === id ? { ...todo, completed: !todo.completed } : todo
22 )
23 })),
24
25 deleteTodo: (id) => set((state) => ({
26 todos: state.todos.filter(todo => todo.id !== id)
27 })),
28
29 setFilter: (filter) => set({ filter }),
30
31 clearCompleted: () => set((state) => ({
32 todos: state.todos.filter(todo => !todo.completed)
33 })),
34
35 // Computed
36 filteredTodos: () => {
37 const { todos, filter } = get()
38 switch (filter) {
39 case 'active':
40 return todos.filter(t => !t.completed)
41 case 'completed':
42 return todos.filter(t => t.completed)
43 default:
44 return todos
45 }
46 },
47
48 stats: () => {
49 const { todos } = get()
50 return {
51 total: todos.length,
52 completed: todos.filter(t => t.completed).length,
53 active: todos.filter(t => !t.completed).length
54 }
55 }
56 }),
57 { name: 'todo-storage' }
58 )
59)
60
61// Usage
62function TodoApp() {
63 const addTodo = useTodoStore((s) => s.addTodo)
64 const filteredTodos = useTodoStore((s) => s.filteredTodos())
65 const stats = useTodoStore((s) => s.stats())
66
67 // Component implementation
68}Zustand is a fantastic choice for React state management!
