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
📌 Creando el proyecto.
📌 Creando una store.
📌 Accediendo a la store.📌 Accediendo a multiples estados.
📌 Creando una acción.
📌 Accediendo al estado almacenado en la store.
📌 Ejecutando la acció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
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
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
🚀 Creando una store.
Primero debemos instalar Zustand:
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
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.
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.
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.
import create from 'zustand';
interface IBook {
amount: number
}
export const useBookStore = create<IBook>( () => ({
amount: 40
}));
🚀 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
⭕ 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"
}));
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
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
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)
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
)
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
}));
⭕ 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 ) => {}
}));
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 }),
}));
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 }))
También puedes hacer acciones asíncronas de la siguiente manera y listo!
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.
updateAmount: () => set({}, true), // clears the entire store, actions included,
⭕ 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 }))
},
}));
⭕ 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
🚀 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)
Excelente post , sería genial un ejemplo con autenticación 🍾
Justamente estoy empezando a usar Zustand para una aplicación personal, se me hizo muy útil tu post, muchas gracias!!!
Me alegra haberte ayudado con este post! 🙌
Muy bueno, pero falta explicar un poco más, como por ejemplo llenar un valor del store con una accion asincrona.
Increible post, me resulta muy interesante y util