Using Zustand with React JS! 🚀
Tags: react, javascript, tutorial, webdev
Published: 2022-08-26 14:16:43

Managing state is a must in modern React JS applications. That's why today I will give you an introduction to "**Zustand**" a popular alternative to manage your status in your applications. 

Any kind of feedback is welcome, thank you and I hope you enjoy the article.🤗

> 🚨 Note: This post requires you to know the basics of **React with TypeScript**.
>

&nbsp;

## Table of Contents.
> 📌 [What is Zustand?](#1)  
>> 📌 [Advantages of using Zustand.](#2)  
>
> 📌 [Creating the project.](#3)  
> 📌 [Creating a store.](#4)  
> 📌 [Accessing the store.](#5)  
>> 📌 [Accessing multiple states.](#6)  
>>> 📌 [Why do we use the shallow function?](#7)  
>
> 📌 [Updating the state.](#8)  
>> 📌 [Creating an action.](#9)  
>> 📌 [Accessing the state stored in the store.](#10)  
>> 📌 [Executing the action.](#11)  
>
> 📌 [Conclusion.](#12)  

&nbsp;


## 🚀 What is Zustand? <a id="1"></a>
Zustand is a small, fast and scalable status management solution. Its state management is centralized and action-based.

Zustand was developed by Jotai and React-spring's creators.

You can use Zustand in both React and some other technology like Angular, Vue JS or even vanilla JavaScript.

Zustand is an alternative to other state managers like Redux, Jotai Recoil, etc.

### ⭕ Advantages of using Zustand. <a id="2"></a>
- Less repeated code (compared to Redux). 
- Easy to understand documentation.
- Flexibility
    - You can use Zustand the simple way, with TypeScript, you can integrate immer for immutability or you can even write code similar to the Redux pattern (reducers and dispatch). 
- It does not wrap the application in a provider as is commonly done in Redux.
- Renders components only when there are changes. 


## 🚀 Creating the project. <a id="3"></a>
We will name the project: **`zustand-tutorial`** (optional, you can name it whatever you like).
```bash
npm init vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we run the following command to navigate to the directory just created.

```bash
cd zustand-tutorial
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```

## 🚀 Creating a store. <a id="4"></a>
First we must install Zustand:
```bash
npm install zustand
```
Once the library is installed, we need to create a folder `src/store` and inside the folder we add a new file called `bookStore.ts` and inside this file, we will create our store.

First we import the zustand package and name it `bookStore.ts`. **create**
```ts
import create from 'zustand';
```

Then we create a constant with the name **useBookStore** (this is because zustand uses hooks underneath and in its documentation it names the stores this way).

To define the store we use the **create** function.

```ts
import create from 'zustand';

export const useBookStore = create();
```

The **create** function takes a callback function as a parameter, which returns an object, to create the store.

```ts
import create from 'zustand';

export const useBookStore = create( () => ({

}));
```

For better autocompletion, we will use an interface to define the properties of our store, as well as the functions.

Then we set the initial value of the properties, in this case the **amount** property will initially be 40.

```ts
import create from 'zustand';

interface IBook {
    amount: number 
}

export const useBookStore = create<IBook>( () => ({
    amount: 40 
}));
```


## 🚀 Accessing the store. <a id="5"></a>
To access our store, we need to import our store. 
In our `src/App.tsx` file we import our store.

Without using providers like in Redux, we can use our store almost anywhere ("almost" because it follows the rules of hooks, since the store is basically a hook underneath). 

Basically we call to our hook, like any other, only that by parameter we must indicate it by means of a callback that property we want to obtain of the store and thanks to the autocomplete it helps us a lot.

```ts
import { useBookStore } from './store/bookStore';
const App = () => {

  const amount = useBookStore(state => state.amount)

  return (
    <div>
      <h1>Books: {amount} </h1>
    </div>
  )
}
export default App
```

### ⭕ Accessing multiple states. <a id="6"></a>
Suppose you have more than one state in your store, for example, let's add the title:

```ts
import create from 'zustand';

interface IBook {
    amount: number
    author: string
}

export const useBookStore = create<IBook>( () => ({
    amount: 40,
    title: "Alice's Adventures in Wonderland"
}));
```

To access more states we could do the following:

**Case 1** - One way is individually, go accessing the state, creating new constants.
```ts
import { useBookStore } from './store/bookStore';
const App = () => {

  const amount = useBookStore(state => state.amount)
  const title = useBookStore(state => state.title)

  return (
    <div>
      <h1>Books: {amount} </h1>
    </div>
  )
}
export default App
```
**Case 2** - But if you want, you can also create a single object with multiple states or properties. And to tell Zustand to diffuse the object shallowly, we must pass the **shallow** function.

```ts
import shallow from 'zustand/shallow'
import { useBookStore } from './store/bookStore';

const App = () => {

  const { amount, title } = useBookStore(
    (state) => ({ amount: state.amount, title: state.title }),
    shallow
  )

  return (
    <div>
      <h1>Books: {amount} </h1>
      <h4>Title: {title} </h4>
    </div>
  )
}
export default App
```

Although it would be better to place the store in a separate hook if it grows too much in terms of properties.

> **In both case 1 and case 2 the components will be rendered when the title and amount change.**

#### 🔴 Why do we use the shallow function? <a id="7"></a>

In the above case where we access several states of the store, we use the **shallow** function, why?

By default if we do not use shallow, Zustand detects changes with strict equality (old === new), which is efficient for atomic states.
```ts
 const amount = useBookStore(state => state.amount)
```  

But in case 2, we are not obtaining an atomic state, but an object (the same happens if we use an array).

```ts
  const { amount, title } = useBookStore(
    (state) => ({ amount: state.amount, title: state.title }),
    shallow
  )
```

**So the default strict equality would not be useful in this case to evaluate objects** and always triggering a re-render even if the object does not change.

So **Shallow** will upload the object/array and compare its keys, if any is different it will recreate again and trigger a new render.



## 🚀 Updating the state. <a id="8"></a>
To update the state in the store we must do it by creating new properties in `src/store/bookStore.ts` adding functions to update modify the store.

In the callback that receives the **create** function, this function receives several parameters, the first one is the **set** function, which will allow us to update the store. 

```ts
export const useBookStore = create<IBook>(( set ) => ({
    amount: 40
}));
```

### ⭕ Creating an action. <a id="9"></a>

First we create a new property to update the **amount** and it will be called **updateAmount** which receives a number as parameter.

```ts
import create from 'zustand'

interface IBook {
    amount: number
    updateAmount: (newAmount: number) => void
}

export const useBookStore = create<IBook>((set) => ({
    amount: 40,
    updateAmount: (newAmount: number ) => {}
}));
```

In the body of the **updateAmount** function we execute the **set** function by sending an object, referencing the property to be updated.

```ts
import create from 'zustand'

interface IBook {
    amount: number
    updateAmount: (newAmount: number) => void
}

export const useBookStore = create<IBook>( (set) => ({
    amount: 40,
    updateAmount: (newAmount: number ) => set({ amount: newAmount }),
}));
```

The **set** function can also receive a function as a parameter, which is useful to get the previous state.

Optionally I scatter the whole state (assuming I have more properties) and only update the state I need, in this case the **amount**.

> **Note**: Spreading properties should also be taken into account when your states are objects or arrays that are constantly changing.

```ts
updateAmount: (newAmount: number ) => set( state => ({ ...state, amount: state.amount + newAmount }))
```

You can also do asynchronous actions as follows and that's it!
```ts
updateAmount: async(newAmount: number ) => {
    // to do fetching data
    set({ amount: newAmount })
}
```

> 💡 Note: the **set** function accepts a second boolean parameter, default is false. **Instead of merging, it will replace the state model**. You must be careful not to delete important parts of your store such as actions.
```ts
  updateAmount: () => set({}, true), // clears the entire store, actions included,

```

### ⭕ Accessing the state stored in the store. <a id="9"></a>

To define the state we use the **set** function, but what if we want to get the values of the state?

Well for that we have the second parameter next to **set**, which is **get()** that gives us access to the state.

```ts
import create from 'zustand'

interface IBook {
    amount: number
    updateAmount: (newAmount: number) => void
}

export const useBookStore = create<IBook>( (set, get) => ({
    amount: 40,
    updateAmount: (newAmount: number ) => {
        
        const amountState = get().amount

        set({ amount: newAmount + amountState })
        //is the same as:
        // set(state => ({ amount: newAmount + state.amount  }))
    },
}));
```


### ⭕ Executing the action. <a id="11"></a>
To execute the action, it is simply to access the property as we have done previously. And we execute it, sending the necessary parameters, that in this case is only a number.

```ts
import { useBookStore } from './store/bookStore';
const App = () => {

  const amount = useBookStore(state => state.amount)
  const updateAmount = useBookStore(state => state.updateAmount)

  return (
    <div>
      
      <h1> Books: {amount} </h1>

      <button 
        onClick={ () => updateAmount(10) } 
      > Update Amount </button>

    </div>
  )
}
export default App
```

## 🚀 Conclusion. <a id="12"></a>
Zustand provides easy access and update of status, which makes it a friendly alternative to other status managers.

In my personal opinion, Zustand has pleased me a lot for its above mentioned features, it is one of my favorite libraries to manage status, as well as Redux Toolkit. You should definitely give it a try to use it in some project 😉.

I hope I have helped you to better understand how this library works and how to use it, thank you very much for coming this far! 🤗  

I invite you to comment if you know of any other important features of Zustand or best practices for code. 🙌