Decoding Zustand: Simplifying State in React
Tags: zustand, react, webdev, frontend
Published: 2024-01-27 07:53:52

## 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`:
```bash
npm install zustand
```
If you are using `yarn`:
```bash
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.

```javascript
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. 
```jsx
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`. 
```jsx
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.

```jsx
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. 
```jsx
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.
```jsx
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:
```jsx
  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: 
```jsx
  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.