Usando Zustand con React JS! 🚀
Tags: react, javascript, tutorial, spanish
Published: 2022-08-26 14:16:55

Gestionar el estado es algo necesario en aplicaciones modernas con React JS. Es por eso que hoy te dare una introducción a "**Zustand**" una alternativa popular para gestionar tu estado en tus aplicaciones. 

Cualquier tipo de Feedback es bienvenido, gracias y espero disfrutes el articulo.🤗

> 🚨 Nota: Este post requiere que sepas las bases de **React con TypeScript**.
>

&nbsp;

## Tabla de contenido
> 📌 [¿Qué es Zustand?](#1)  
>> 📌 [Ventajas de usar Zustand.](#2)  
>
> 📌 [Creando el proyecto.](#3)  
> 📌 [Creando una store.](#4)  
> 📌 [Accediendo a la store.](#5)  
>> 📌 [Accediendo a multiples estados.](#6)  
>>> 📌 [¿Por qué usamos la función shallow?.](#7)  
>
> 📌 [Actualizando el estado.](#8)  
>> 📌 [Creando una acción.](#9)  
>> 📌 [Accediendo al estado almacenado en la store.](#10)  
>> 📌 [Ejecutando la acción.](#11)  
>
> 📌 [Conclusión.](#12)  

&nbsp;

## 🚀 ¿Qué es Zustand? <a id="1"></a>
Zustand es una solución de gestión de estados pequeña, rápida y escalable. Su gestión de estado es centralizada y basada en acciones.
Zustand fue desarrollado por los creadores de Jotai y React-spring's 
Puedes usar Zustand tanto en React como en alguna otra tecnología como Angular, Vue JS o incluso en JavaScript vanilla.
Zustand es una alternativa a otros gestores de estado como Redux, Jotai Recoil, etc.

### ⭕ Ventajas de usar Zustand. <a id="2"></a>
- Menos código repetido (comparado con Redux). 
- Documentación fácil de entender.
- Flexibilidad
    - Puedes usar Zustand de la forma simple, con TypeScript, puedes integrar immer para la inmutabilidad o incluso puedes escribir código parecido al patron Redux (reducers y dispatch). 
- No envuelve la aplicación en un proveedor como comúnmente se hace en Redux.
- Vuelve a renderizar los componentes solo cuando hay cambios. 

## 🚀 Creando el proyecto. <a id="3"></a>
Al proyecto le colocaremos el nombre de: **`zustand-tutorial`** (opcional, tu le puedes poner el nombre que gustes).
```bash
npm init vite@latest
```
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.  
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

```bash
cd zustand-tutorial
```
Luego instalamos las dependencias.
```bash
npm install
```
Después abrimos el proyecto en un editor de código (en mi caso VS code).
```bash
code .
```

## 🚀 Creando una store. <a id="4"></a>
Primero debemos instalar Zustand:
```bash
npm install zustand
```
Una vez instalada la librería, necesitamos crear una carpeta `src/store` y dentro de la carpeta agregamos un nuevo archivo llamado `bookStore.ts` y dentro de este archivo, crearemos nuestra store.

Primero importamos el paquete de zustand y lo nombramos **create**
```ts
import create from 'zustand';
```

Luego creamos una constante con el nombre **useBookStore** (esto es porque zustand usa hooks por debajo y en su documentación nombre las stores de esta manera).

Para definir la store usamos la función **create**.

```ts
import create from 'zustand';

export const useBookStore = create();
```

La función **create** toma una función callback como parámetro, que retorna un objeto, para crear la store.

```ts
import create from 'zustand';

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

}));
```

Para mejor auto completado, usaremos una interfaz para definir las propiedades de nuestra store, así como las funciones.

Luego establecemos el valor inicial de las propiedades, en este caso la propiedad **amount** inicialmente sera 40.

```ts
import create from 'zustand';

interface IBook {
    amount: number 
}

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

## 🚀 Accediendo a la store. <a id="5"></a>
Para acceder a nuestra store, necesitamos importar dicha store. 
En nuestro archivo `src/App.tsx` importamos nuestra store.

Sin necesidad de usar proveedores como en Redux, podemos usar nuestra store casi en cualquier lugar ("casi" ya que sigue las reglas de los hooks, ya que la store básicamente es un hook por debajo). 

Básicamente llamamos a nuestro hook, como cualquier otro, solo que por parámetro debemos indicarle mediante un callback que propiedad queremos obtener del store y gracias al auto completado nos ayuda mucho.

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

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

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

### ⭕ Accediendo a multiples estados. <a id="6"></a>
Supongamos que tienes mas de un estado en tu store, por ejemplo, agregamos el titulo:

```ts
import create from 'zustand';

interface IBook {
    amount: number
    author: string
}

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

Para acceder a mas estados podríamos hacer lo siguiente:

**Caso 1** - Una forma es de manera individual, ir accediendo al estado, creando nuevas constantes.
```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
```
**Caso 2** - Pero si quieres, también puedes crear un único objeto con multiples estados o propiedades. Y para decirle a Zustand que difunda el objeto superficialmente, debemos pasar la función **shallow**

```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
```

Aunque lo mejor seria también el store colocarlo en un hook aparte si es que llega a crecer demasiado en cuestión de propiedades

>  **Tanto en el caso 1 como el caso 2 los componentes se volverán a renderizar cuando el title y amount cambien.**

#### 🔴 ¿Por qué usamos la función shallow? <a id="7"></a>

En el caso anterior donde accedemos a varios estados de la store, usamos la función **shallow**, ¿por qué?

Por defecto si no usamos shallow, Zustand detecta los cambios con igualdad estricta (old === new), lo cual es eficiente para estados atómicos
```ts
 const amount = useBookStore(state => state.amount)
```  

Pero en el caso 2, no estamos obteniendo un estado atómico, sino un objeto (pasa lo mismo si usamos un arreglo).

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

**Por lo que la igualdad estricta por defecto no seria util en este caso para evaluar objetos** y provocando siempre un re-render aunque el objeto no cambie.

Asi que **Shallow** subirá el objeto/arreglo y comparara sus claves, si alguna es diferente se recreara de nuevo y se dispara un nuevo render.

## 🚀 Actualizando el estado. <a id="8"></a>
Para actualizar el state en la store debemos hacerlo creando nuevas propiedades en `src/store/bookStore.ts` agregando funciones para actualizar modificar el store.

En el callback que recibe la función **create**, dicha función recibe varios parámetros, el primero es la función **set**, el cual nos permitirá actualizar la store. 

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

### ⭕ Creando una acción. <a id="9"></a>

Primero creamos una nueva propiedad para actualizar el **amount** y se llamara **updateAmount** el cual recibe un número como parámetro.

```ts
import create from 'zustand'

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

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

El el cuerpo de la función **updateAmount** ejecutamos la función **set** mandando un objeto, haciendo referencia a la propiedad a actualizar.

```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 }),
}));
```

La función **set** también puede recibir una función como parámetro, lo cual es util para obtener el estado anterior.

Opcionalmente hago esparzo todo el estado (suponiendo que tengo más propiedades) y solo actualizo el estado que necesito, en este caso el **amount**.

> **Nota**: Lo de esparcir propiedades tómalo en cuenta también cuando tus estados sean objetos o arreglos que cambian constantemente.

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

También puedes hacer acciones asíncronas de la siguiente manera y listo!
```ts
updateAmount: async(newAmount: number ) => {
    // to do fetching data
    set({ amount: newAmount })
}
```

> 💡 Nota: la función **set** acepta un segundo parámetro booleano, por defecto es falso. **En lugar de fusionar, remplazara el modelo del estado**. Debe tener cuidado de no borrar partes importantes de su store como las acciones.
```ts
  updateAmount: () => set({}, true), // clears the entire store, actions included,

```

### ⭕ Accediendo al estado almacenado en la store. <a id="9"></a>

Para definir el estado usamos la función **set**, pero y si queremos obtener los valores del estado?

Bueno para eso tenemos el segundo parámetro a lado del **set**, que es **get()** que nos da acceso al estado.

```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  }))
    },
}));
```


### ⭕ Ejecutando la acción. <a id="11"></a>
Para ejecutar la acción, es simplemente acceder a la propiedad como lo hemos realizado con anterioridad. Y la ejecutamos, mandando los parámetros necesarios, que en este caso es solo un numero.

```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
```

## 🚀 Conclusión. <a id="12"></a>
Zustand proporciona un fácil acceso y actualización del estado, lo que lo convierte en una alternativa amigable a otros gestores de estado.

En opinion personal, Zustand me ha agradado bastante por sus características antes mencionadas, es una de mis librerías favoritas para gestionar el estado, así como Redux Toolkit. Sin duda deberías de darle una oportunidad para usarla en algún proyecto 😉.

Espero haberte ayudado a entender mejor el como funciona y como usar esta librería,muchas gracias por llegar hasta aquí! 🤗  

Te invito a que comentes si es que conoces alguna otra característica importante de Zustand o mejores practicas para el código. 🙌