DEV Community

Cover image for Usando Zustand con React JS! 馃殌
Franklin Martinez
Franklin Martinez

Posted on

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. 馃檶

Top comments (5)

Collapse
 
danielsuarezdev profile image
DanielSuarez

Excelente post , ser铆a genial un ejemplo con autenticaci贸n 馃嵕

Collapse
 
izakntun profile image
Isaac Cant煤n

Justamente estoy empezando a usar Zustand para una aplicaci贸n personal, se me hizo muy 煤til tu post, muchas gracias!!!

Collapse
 
franklin030601 profile image
Franklin Martinez

Me alegra haberte ayudado con este post! 馃檶

Collapse
 
hernanarica profile image
Hern谩n Arica

Muy bueno, pero falta explicar un poco m谩s, como por ejemplo llenar un valor del store con una accion asincrona.

Collapse
 
flash010603 profile image
Usuario163

Increible post, me resulta muy interesante y util