Building Scalable State Management with Zustand in React
When building complex web applications using React, one of the common challenges developers face is managing the application state. While libraries like Redux have been widely used to solve this problem, there's a modern alternative that is gaining popularity for its simplicity and performance β Zustand. In this blog post, weβll explore how to leverage Zustand for scalable state management in React applications.
Zustand (German for "state") is a small, fast, and scalable state management solution for React applications. Created by the authors of Jotai and React-Spring, Zustand is designed to make state management simpler and more ergonomic compared to other solutions like Redux or MobX.
Its key features include:
Zustand is extremely lightweight and easy to install. Run the following command to add it to your project:
npm install zustand
Or if you're using Yarn:
yarn add zustand
Letβs walk through the process of creating a store where we manage a simple counter state. Zustand uses hooks to manage and retrieve state.
import create from 'zustand' const useStore = create((set) => ({ count: 0, increase: () => set((state) => ({ count: state.count + 1 })), decrease: () => set((state) => ({ count: state.count - 1 })), }))
This uses a single store to keep track of a count
and functions to manipulate it. The set
function allows you to update the state in a concise way.
In your React components, you can use your store hook to read and update the state:
import React from 'react' import useStore from './store' function Counter() { const { count, increase, decrease } = useStore() return ( <div> <h1>{count}</h1> <button onClick={increase}>Increase</button> <button onClick={decrease}>Decrease</button> </div> ) } export default Counter
Zustand ensures that components only re-render when the part of the state they use changes, which results in better performance.
You can optimize rerenders by selecting only the parts of the state you need:
const count = useStore((state) => state.count) const increase = useStore((state) => state.increase)
This will limit re-renders to only when the selected value changes.
Zustand supports middleware out of the box. One of the most commonly used middleware is persist
, which stores state in localStorage:
import create from 'zustand' import { persist } from 'zustand/middleware' const useStore = create(persist( (set) => ({ count: 0, increase: () => set((state) => ({ count: state.count + 1 })), decrease: () => set((state) => ({ count: state.count - 1 })) }), { name: 'counter-storage', } ))
Now the state persists across page reloads!
You can also include async actions directly in your store without any extra boilerplate:
const useStore = create((set) => ({ data: [], loading: false, fetchData: async () => { set({ loading: true }) const res = await fetch('https://api.example.com/data') const json = await res.json() set({ data: json, loading: false }) } }))
Let's implement a simple auth store that could be used across your React app:
const useAuthStore = create((set) => ({ user: null, token: null, login: async (username, password) => { const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }), }) const data = await res.json() set({ user: data.user, token: data.token }) }, logout: () => set({ user: null, token: null }) }))
This auth store can now be accessed anywhere in your application.
Feature | Zustand | Redux |
---|---|---|
Setup | π’ Minimal | π΄ Verbose |
Boilerplate | π’ Low | π΄ High |
Middleware | π’ Built-in | π’ Yes (via Redux toolkit) |
Async Actions | π’ Easy | π‘ Extra setup needed |
Community | π‘ Growing | π’ Established |
In short, Zustand reduces boilerplate and complexity, making it more approachable for smaller to medium-sized applications or even large ones if structured properly.
As your application grows, managing a single monolithic store can become difficult. Zustand makes it easy to break your state into separate modules:
// stores/userStore.js export const useUserStore = create((set) => ({ /* ... */ })) // stores/uiStore.js export const useUIStore = create((set) => ({ /* ... */ })) // Import and combine in components or utilities
You can freely combine smaller stores as needed in your components, improving modularity and maintainability.
Testing Zustand is straightforward. Here's an example of how you can test a store independently:
import { act } from 'react-dom/test-utils' import { renderHook } from '@testing-library/react-hooks' import useStore from './store' test('should increase count', () => { const { result } = renderHook(() => useStore()) act(() => result.current.increase()) expect(result.current.count).toBe(1) })
Zustand state does not show up in the Redux DevTools by default, but you can still debug it using React's component tree and logs. There is also a third-party plugin called zustand-devtools-extension
if you want Redux-style debugging.
Zustand is one of the most elegant and minimalist solutions available for managing state in modern React applications. Its simplicity, performance, and flexibility make it a compelling choice β especially for developers tired of Redux boilerplate.
Whether you're building a small project or scaling a large web application, Zustand allows you to focus more on building features and less on managing state complexities.
Happy coding! π
π§βπ» If you need help building scalable and maintainable web applications with Zustand or other modern frontend technologies β we offer such services.
Information