DEV Community

Cover image for Decoding Zustand: Simplifying State in React
Debajit Mallick
Debajit Mallick

Posted on • Updated on

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
Enter fullscreen mode Exit fullscreen mode

If you are using yarn:

yarn add zustand
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 })),
}))
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
          }
        }
      }
    })),
Enter fullscreen mode Exit fullscreen mode

Using Immer, it will just be a one line code, like this:

  increment: () =>
    set(produce((state: State) => { ++state.deep.nested.obj.count })),
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)