Decoding Zustand: Simplifying State in React
Introduction
If you are a React Developer, you are already familiar with the topic state management. In the State Management world, Redux is quite popular. But, sometimes, Redux is frustrating because of its lengthy boilerplate code. In places like that, Zustand comes as a saviour.
What is Zustand?
Zustand is a state management library for React that offers a minimalistic yet powerful API. It provides a simple way to manage the state of your application without the need for complex abstractions.
Getting Started with Zustand
To use Zustand in your existing React project, use the following command in the terminal: If you are using npm
:
npm install zustand
If you are using yarn
:
yarn add zustand
Next, let's create a store to save the global state data. If you are familiar with the basics of the react-redux, you will understand it easily. Let's add a folder named store in your React project and add an store.js file to it. Now, let's create a store in the file, to save global state data. To create a store, we are using the create
function from Zustand. In Zustand, everything is hook-based. So we are naming it as useStore hook.
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: (qty) => set((state) => ({ count: state.count + qty })),
decrement: (qty) => set((state) => ({ count: state.count - qty })),
}));
export default useStore;
In this example, we have created a simple store to save the value of the count. Here, the set
function is the state setter function, which helps us to set new values. Also, increment
and decrement
are the actions to change the state. Now, as our store is created, let's add it to our components.
import useStore from './useStore';
const App = () => {
const { count, increment, decrement } = useStore();
return (
<div>
<h1>Counter App</h1>
<p>Count: {count}</p>
<button onClick={() => {increment(1)}}>Increment</button>
<button onClick={() => {decrement(1)}}>Decrement</button>
</div>
);
};
export default App;
To use the store, we just need to import the useStore
hook. And that will do all the tasks for you. You can import the increment
, decrement
and count
from it. And use it anywhere in your code.
Devtools and Persist
Now, you may ask that in Redux, you are using Redux Devtools, in Zustand, how to use it? We can use that in Zustand also. Just import the devtools
from the zustand middleware
.
import create from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create(
devtools((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
);
export default useStore;
Now, you can open the Redux Devtools and use it with Zustand.
Sometimes, we want to persist the data in the Local Storage or Session Storage. In Zustand, that is also pretty easy using the persist
middleware.
import create from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(
persist(
devtools((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
})),
{
name: 'counter-app',
}
)
);
export default useStore;
Zustand using TypeScript
To use with TypeScript is also pretty easy. You can define the types or interfaces first and then refer to them in the create
function.
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
Use Zustand in Redux way
If you like the structure of Redux, you can also generate that in Zustand. We can create an action named dispatch
to handle all the store updates.
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
type State = {
count: number;
};
type Actions = {
dispatch: (action: Action) => void;
};
type Action = {
type: 'increment' | 'decrement';
qty: number;
};
const counterReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment': {
return {
count: state.count + action.qty,
};
}
case 'decrement': {
return {
count: state.count - action.qty,
};
}
default: {
return state;
}
}
};
const useStore = create<State & Actions>()(
devtools((set) => ({
count: 0,
dispatch: (action: Action) => set((state) => counterReducer(state, action)),
}))
);
export default useStore;
Immer Integration
Zustand seamlessly integrates with Immer, a library that makes working with immutable state easy and intuitive. This allows you to update your state in a mutable way while ensuring immutability under the hood. Let's say you have a deeply nested object in your store. Now, you want to update the count, which is a deeply nested entity. Normally, you can do that like this:
increment: () =>
set((state) => ({
deep: {
...state.deep,
nested: {
...state.deep.nested,
obj: {
...state.deep.nested.obj,
count: state.deep.nested.obj.count + 1
}
}
}
})),
Using Immer, it will just be a one line code, like this:
increment: () =>
set(produce((state: State) => { ++state.deep.nested.obj.count })),
Conclusion
Zustand is a powerful yet lightweight state management library for React, offering a simple API with excellent performance. Whether you're working on a small project or a large-scale application, Zustand's flexibility and ease of use make it a compelling choice for managing state in your React applications. If you like this blog and want to learn more about Frontend Development and Software Enginnering follow me on hashnode.
Subscribe to my newsletter
Read articles from Debajit Mallick directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Debajit Mallick
Debajit Mallick
I am a Software Engineer with 3+ years of experience, currently at P360. I have a passion for creating intuitive web interfaces and actively contribute to tech communities as the Organizer of GDG Siliguri, ex-Microsoft Learn Student Ambassador, and former Hack Club Lead. As a tech speaker, I’ve presented at events like FrontendDays Siliguri, GDG Bhopal, and Azure DevDay. I’m also passionate about hackathons and open-source: Smart India Hackathon 2020 winner, HacktoberFest contributor, and mentor to the winning team of SIH 2022.