Usando Zustand con React JS! 🚀

franklin030601

Franklin Martinez

Posted on August 26, 2022

Usando Zustand con React JS! 🚀

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.

 

Tabla de contenido

📌 ¿Qué es Zustand?

📌 Ventajas de usar Zustand.

📌 Creando el proyecto.

📌 Creando una store.

📌 Accediendo a la store.

📌 Accediendo a multiples estados.

📌 ¿Por qué usamos la función shallow?.

📌 Actualizando el estado.

📌 Creando una acción.

📌 Accediendo al estado almacenado en la store.

📌 Ejecutando la acción.

📌 Conclusión.

 

🚀 ¿Qué es Zustand?

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.

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

Al proyecto le colocaremos el nombre de: zustand-tutorial (opcional, tu le puedes poner el nombre que gustes).

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

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.

cd zustand-tutorial
Enter fullscreen mode Exit fullscreen mode

Luego instalamos las dependencias.

npm install
Enter fullscreen mode Exit fullscreen mode

Después abrimos el proyecto en un editor de código (en mi caso VS code).

code .
Enter fullscreen mode Exit fullscreen mode

🚀 Creando una store.

Primero debemos instalar Zustand:

npm install zustand
Enter fullscreen mode Exit fullscreen mode

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

import create from 'zustand';
Enter fullscreen mode Exit fullscreen mode

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.

import create from 'zustand';

export const useBookStore = create();
Enter fullscreen mode Exit fullscreen mode

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

import create from 'zustand';

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

}));
Enter fullscreen mode Exit fullscreen mode

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.

import create from 'zustand';

interface IBook {
    amount: number 
}

export const useBookStore = create<IBook>( () => ({
    amount: 40 
}));
Enter fullscreen mode Exit fullscreen mode

🚀 Accediendo a la store.

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.

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

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

  return (
    <div>
      <h1>Books: {amount} </h1>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

⭕ Accediendo a multiples estados.

Supongamos que tienes mas de un estado en tu store, por ejemplo, agregamos el titulo:

import create from 'zustand';

interface IBook {
    amount: number
    author: string
}

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

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.

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

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

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

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?

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

 const amount = useBookStore(state => state.amount)
Enter fullscreen mode Exit fullscreen mode

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

  const { amount, title } = useBookStore(
    (state) => ({ amount: state.amount, title: state.title }),
    shallow
  )
Enter fullscreen mode Exit fullscreen mode

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.

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.

export const useBookStore = create<IBook>(( set ) => ({
    amount: 40
}));
Enter fullscreen mode Exit fullscreen mode

⭕ Creando una acción.

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

import create from 'zustand'

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

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

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

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

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.

updateAmount: (newAmount: number ) => set( state => ({ ...state, amount: state.amount + newAmount }))
Enter fullscreen mode Exit fullscreen mode

También puedes hacer acciones asíncronas de la siguiente manera y listo!

updateAmount: async(newAmount: number ) => {
    // to do fetching data
    set({ amount: newAmount })
}
Enter fullscreen mode Exit fullscreen mode

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

  updateAmount: () => set({}, true), // clears the entire store, actions included,

Enter fullscreen mode Exit fullscreen mode

⭕ Accediendo al estado almacenado en la store.

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.

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

⭕ Ejecutando la acción.

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.

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

🚀 Conclusión.

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

💖 💪 🙅 🚩
franklin030601
Franklin Martinez

Posted on August 26, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Usando Zustand con React JS! 🚀
react Usando Zustand con React JS! 🚀

August 26, 2022