Global State Management with Zustand

Have you ever wondered what specific challenges in the React ecosystem led to the creation of Zustand?
In the ever-evolving landscape of React, state management has always been a central challenge. While powerful libraries like Redux offered structure, they often came with the cost of heavy boilerplate. On the other hand, React's built-in Context API, while simple, presented performance pitfalls, re-rendering every component that consumed it on any state change. It was out of these challenges that Zustand was born—a small, fast, and scalable state management solution designed with a "less is more" philosophy.
What Problems Does Zustand Solve?
To appreciate Zustand, we must first understand the problems it was built to fix.
1. Escaping Redux Boilerplate 🤯
For years, Redux was the de-facto standard for global state. But its verbosity was a common complaint. Setting up actions, reducers, dispatchers, and connecting them all required a significant amount of code before you could even manage a single piece of state.
Zustand's solution is radical simplicity. A store is a simple object created with a function. State and the actions that modify it live together.
// The old way with Redux might involve multiple files... // actions.js, reducers.js, store.js
// The Zustand way: import { create } from 'zustand';
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
2. Fixing Context API's Performance Bottleneck 🐌
The React Context API is great for passing state down the component tree without prop-drilling. However, its major flaw is performance. When any value in the context changes, every single component consuming that context re-renders.
Imagine a user profile context with name
, email
, and theme
. If a component only displays the name
, it will still re-render when the user changes the theme
. This causes unnecessary renders and can slow your app down significantly.
Zustand solves this with selective subscriptions. Components can "select" only the pieces of state they care about. They will only re-render if that specific piece of state changes.
function BearCounter() {
// This component only cares about the 'bears' count.
// It will NOT re-render if another part of the store changes.
const bears = useBearStore((state) => state.bears);
return <h1>{bears} around here ...</h1>;
}
3. Solving Critical Technical Hurdles ✅
Zustand was also engineered to solve modern React challenges that other solutions struggle with:
The Zombie Child Problem: This occurs when a component makes an async request,
unmounts
before the request completes, and then tries to update its state upon completion, causing a memory leak or error. Zustand’s hooks automatically handle unsubscribing from the store when a component unmounts, elegantly preventing this issue.React Concurrency & Tearing: In React 18+,
concurrent rendering
can cause a UI "tear," where different components display different versions of the same state in a single render. Zustand is built with this in mind, using the officialuseSyncExternalStore
hook to guarantee that your UI always has a consistent and up-to-date view of your state.Context Loss: React Context is only accessible to components wrapped within its
<Provider>
. You can't access it from utility functions or other non-React parts of your application. A Zustand store, however, is a vanilla JavaScript module. You can access or update your state from anywhere in your code, completely decoupling your state logic from your UI tree.
Store creation and management with Zustand 👨💻
Let's build a simple store.
Step 1: Installation
First, add Zustand to your project.
npm install zustand
# or
yarn add zustand
Step 2: Creating Your Store
Create a file, for example src/store/useAuthStore.js
in frontend, and define your store. We'll manage a user's authentication status and name.
// src/store/useAuthStore.js
import { create } from "zustand";
//variable and methods which we will use globally
export const useAuthStore = create((set) => ({
authUser: null,
isLoggingIn: false,
login: async (data) => {
//updating the state
set({ isLoggingIn: true });
try {
//call your api
} catch (error) {
console.log("error loging In");
} finally {
set({ isLoggingIn: false });
}
},
}));
Here, set
is the function you use to update the state.
Step 3: Using the Store in Your Components
Now, you can use the useAuthStore
hook in any component to access state and actions.
import React, { useState } from "react";
import { useAuthStore } from "../../store/useAuthStore";
const Login = () => {
const { login, isLoggingIn } = useAuthStore();
const onSubmit = async (data) => {
try {
await login(data);
} catch (error) {
console.error("logging in failed:", error);
}
};
return (
<div>
{/*Handling ui*/}
</div>
);
};
export default Login;
Beyond the Basics: Middleware & Async Actions
Zustand's power extends further with middleware. You can easily add functionality like:
devtools
: Connect your store to the Redux DevTools for easy debugging.persist
: Persist your store's state tolocalStorage
orsessionStorage
.immer
: Mutate state directly in a safe, immutable way.
Why zustand over redux?
Simple and un-opinionated
Makes hooks the primary means of consuming state
Doesn't wrap your app in context providers
Why zustand over context?
Less boilerplate
Renders components only on changes
Centralized, action-based state management
State manager integration architecture
State manager integration architecture refers to the patterns and principles used to connect a state management library to a user interface (UI) framework. It defines how application data flows, how state is updated, and how side effects like API calls are handled, ensuring a predictable and maintainable application structure.
For more Details:
→ zustand Docs
: https://github.com/pmndrs/zustand?tab=readme-ov-file
Subscribe to my newsletter
Read articles from Syed Wasif Hussain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
