Introduction to Zustand
Zustand is a small, fast, and highly scalable state-management library built by the creators of React Spring. In the ever-evolving landscape of React state management, Zustand has emerged as a favorite among developers due to its minimalistic API and lack of boilerplate. Unlike Redux, which requires actions, reducers, and store configurations, Zustand allows you to create a store with just a single function call.
Why does Zustand matter? First, it does not require wrapping your application in a Context Provider. The store is a standalone hook that can be used anywhere, even outside of React components. Second, it prevents unnecessary re-renders by allowing components to subscribe to specific slices of state. Finally, its learning curve is incredibly gentle, making it accessible for beginners while remaining powerful enough for expert-level applications.
Getting Started: The Basics
Installation
To begin your journey with Zustand, you need to install it in your React project. Open your terminal and run the following command:
npm install zustand
# or
yarn add zustand
Creating Your First Store
A store in Zustand is created using the create function. This function takes a callback that returns an object containing your state and the functions to update that state. Let's create a simple counter store.
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
export default useCounterStore;
Consuming State in Components
Because the store itself is a custom hook, you can use it directly inside your React components. You can call the hook to get the entire state, or destructure it to get specific properties and actions.
import React from 'react';
import useCounterStore from './useCounterStore';
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
Intermediate Concepts: Structuring State
Selecting State Slices
While destructuring the store works, it can cause your component to re-render even if the specific piece of state you are using hasn't changed. To optimize performance, you should select only the slices of state you need. Zustand allows you to pass a selector function to the hook.
import React from 'react';
import useCounterStore from './useCounterStore';
function CounterDisplay() {
// Only re-renders when 'count' changes
const count = useCounterStore((state) => state.count);
return <h1>Count: {count}</h1>;
}
function CounterControls() {
// Only re-renders when 'increment' or 'decrement' changes (which is never, as functions are stable)
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
return (
<div>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
Handling Asynchronous Actions
One of the best features of Zustand is how naturally it handles asynchronous operations. You do not need special middleware like redux-thunk. You simply write an async function in your store and call set when the promise resolves.
import { create } from 'zustand';
const useUserStore = create((set) => ({
user: null,
isLoading: false,
error: null,
fetchUser: async (userId) => {
set({ isLoading: true, error: null });
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
set({ user: data, isLoading: false });
} catch (err) {
set({ error: err.message, isLoading: false });
}
},
}));
Advanced Zustand: Expert Techniques
Using Middleware
Zustand supports middleware to enhance store functionality. Two of the most common are persist (to save state to localStorage) and immer (to handle immutable updates with a mutable syntax). Here is how you can use the persist middleware.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useSettingsStore = create(
persist(
(set) => ({
theme: 'light',
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
}),
{
name: 'theme-storage', // name of the item in localStorage
}
)
);
TypeScript Integration
For expert developers, type safety is paramount. Zustand works seamlessly with TypeScript. You can define an interface for your store and pass it to the create function to get full type inference.
import { create } from 'zustand';
interface BearState {
bears: number;
addBear: () => void;
removeBear: () => void;
}
const useBearStore = create<BearState>((set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
removeBear: () => set((state) => ({ bears: state.bears - 1 })),
}));
Best Practices for Zustand
- Keep Stores Modular: Do not put all your application state into a single massive store. Create separate stores for different domains (e.g.,
useAuthStore,useCartStore,useUIStore). - Always Use Selectors: Avoid calling the hook without a selector function unless you genuinely need the entire state. This prevents unnecessary re-renders and keeps your app fast.
- Separate UI State from Domain State: Keep transient UI state (like modals opening/closing) separate from core business logic state (like user data or fetched entities).
- Leverage the Store Outside React: Because the store is just a function, you can access and update state in regular JavaScript files using
useStore.getState()anduseStore.setState(). This is incredibly useful for API interceptors or utility functions.
Conclusion
Zustand provides a refreshing approach to state management in React. By starting with the basic create function and moving towards selectors, async actions, and middleware, you can build a robust architecture without the heavy boilerplate of traditional state managers. Whether you are building a small widget or a large-scale enterprise application, following this learning path and adhering to best practices will ensure your state remains predictable, performant, and easy to maintain.