Decoding Zustand: Simplifying State in React

Debajit MallickDebajit Mallick
5 min read

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.

0
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.