Concepto clave
Zustand es una biblioteca de gestión de estado global minimalista para React que resuelve problemas de escalabilidad y complejidad que aparecen en aplicaciones Expo avanzadas. A diferencia de Redux, que requiere boilerplate significativo con acciones, reducers y middlewares, Zustand utiliza un enfoque basado en hooks que es más intuitivo y menos verboso.
Imagina que estás construyendo una aplicación de comercio electrónico con Expo. En lugar de tener que pasar props manualmente a través de múltiples componentes (prop drilling) o configurar un sistema complejo de contexto, Zustand te permite crear un store centralizado que cualquier componente puede acceder directamente. Es como tener una caja fuerte compartida en una oficina: todos los empleados (componentes) saben dónde está y pueden acceder a lo que necesitan sin tener que pedir permiso a través de una cadena de mando.
La verdadera ventaja de Zustand en aplicaciones Expo avanzadas es su optimización automática. Cuando un componente se suscribe a solo una parte del estado, solo se rerenderiza cuando esa parte específica cambia. Esto es crucial para aplicaciones móviles donde el rendimiento afecta directamente la experiencia del usuario y el consumo de batería.
Cómo funciona en la práctica
Implementar Zustand en una aplicación Expo sigue un flujo claro. Primero, instalas la biblioteca: npm install zustand o yarn add zustand. Luego, creas tu store definiendo el estado inicial y las acciones que lo modifican.
Veamos el proceso paso a paso:
- Crear un archivo store.js donde defines tu estado global
- Usar la función create de Zustand para generar tu store
- Definir el estado inicial y las funciones que lo modifican (set)
- En tus componentes, importar el hook personalizado generado
- Seleccionar solo las partes del estado que cada componente necesita
La magia está en cómo Zustand maneja las suscripciones. Cuando usas el hook en un componente, automáticamente se suscribe a los cambios del estado, pero solo se rerenderiza si las propiedades específicas que seleccionaste han cambiado. Esto elimina renders innecesarios que son comunes en soluciones de contexto nativo de React.
Codigo en accion
Veamos un ejemplo real de una aplicación de tareas con Expo. Primero, el enfoque tradicional con Context API:
// Antes: Context API complejo
export const TasksContext = React.createContext();
export function TasksProvider({ children }) {
const [tasks, setTasks] = useState([]);
const [filter, setFilter] = useState('all');
const addTask = (task) => {
setTasks([...tasks, task]);
};
const value = {
tasks,
filter,
addTask,
setFilter
};
return (
{children}
);
}
// En un componente hijo profundo:
function TaskList() {
const { tasks, filter } = useContext(TasksContext);
// Este componente se rerenderiza aunque solo cambie filter
return (
{tasks.filter(t => filter === 'all' || t.status === filter).map(task => (
))}
);
}Ahora, el mismo ejemplo optimizado con Zustand:
// Despues: Zustand optimizado
import create from 'zustand';
const useTaskStore = create((set) => ({
tasks: [],
filter: 'all',
addTask: (task) => set((state) => ({
tasks: [...state.tasks, task]
})),
setFilter: (filter) => set({ filter }),
clearCompleted: () => set((state) => ({
tasks: state.tasks.filter(t => !t.completed)
}))
}));
// En el mismo componente hijo:
function TaskList() {
// Solo se suscribe a tasks y filter, no a todo el store
const tasks = useTaskStore((state) => state.tasks);
const filter = useTaskStore((state) => state.filter);
// Solo se rerenderiza si tasks o filter cambian
const filteredTasks = tasks.filter(t =>
filter === 'all' || t.status === filter
);
return (
{filteredTasks.map(task => (
))}
);
}Errores comunes
Al implementar Zustand en aplicaciones Expo, estos son los errores más frecuentes:
- Suscribirse a todo el store: Usar
useTaskStore()sin selector hace que el componente se rerenderice con cualquier cambio en el store. Siempre usa selectores específicos:useTaskStore(state => state.tasks). - Mutación directa del estado: En Zustand, nunca modifiques el estado directamente. Siempre usa la función
setproporcionada. Mal:state.tasks.push(newTask). Bien:set(state => ({tasks: [...state.tasks, newTask]})). - Olvidar la optimización de selectores: Para selectores complejos que realizan cálculos costosos, usa
useMemoo la funciónshallowde Zustand para evitar recálculos en cada render. - Store demasiado grande: Agrupar todo el estado de la aplicación en un solo store puede volverse inmanejable. Divide stores lógicamente por dominio (authStore, cartStore, userStore).
- No manejar estado asíncrono correctamente: Para operaciones async, usa patrones claros. Zustand no prescribe cómo manejar async, pero un patrón común es tener estados de loading/error separados.
Checklist de dominio
Para verificar que dominas Zustand en aplicaciones Expo, asegúrate de poder:
- Crear un store con estado inicial y acciones de modificación usando la función
create - Usar selectores específicos en componentes para optimizar rerenders
- Implementar correctamente actualizaciones de estado inmutables con la función
set - Dividir stores lógicamente cuando la aplicación escala (múltiples stores)
- Manejar estado asíncrono con patrones claros (loading, error, data)
- Integrar Zustand con otras bibliotecas como React Navigation para estado de navegación
- Usar middleware de Zustand para logging, persistencia o devtools en desarrollo
Refactorizar una aplicación Expo de comercio electrónico de Context API a Zustand
En este ejercicio práctico, refactorizarás una aplicación Expo existente que usa Context API para gestión de estado a usar Zustand optimizado.
- Contexto inicial: Descarga o clona la aplicación base que tiene:
- Un carrito de compras con Context API
- Estado de usuario (autenticación) con Context separado
- Componentes que sufren de prop drilling y rerenders innecesarios
- Paso 1: Instalación y setup
- Instala Zustand en el proyecto:
npm install zustand - Crea una carpeta
stores/en la raíz del proyecto
- Instala Zustand en el proyecto:
- Paso 2: Crear el store del carrito
- Crea
stores/cartStore.js - Define el estado inicial: items (array), total (number)
- Implementa acciones: addItem, removeItem, clearCart, updateQuantity
- Asegúrate que todas las acciones usen el patrón inmutable
- Crea
- Paso 3: Crear el store de autenticación
- Crea
stores/authStore.js - Estado: user (object/null), isLoading (boolean), error (string/null)
- Acciones: login (async), logout, clearError
- Implementa manejo adecuado de estados loading/error
- Crea
- Paso 4: Refactorizar componentes
- Reemplaza todos los
useContextpor hooks de Zustand - En cada componente, usa selectores específicos (no todo el store)
- Para el componente CartSummary, solo suscríbete a
total - Para ProductList, solo suscríbete a
itemspara mostrar cantidad en carrito
- Reemplaza todos los
- Paso 5: Optimización avanzada
- En componentes con cálculos costosos (como filtrado de productos), usa
useMemo - Implementa persistencia básica para el carrito usando localStorage (AsyncStorage en Expo)
- Agrega logging en desarrollo con middleware de Zustand
- En componentes con cálculos costosos (como filtrado de productos), usa
- Entrega: Proporciona:
- Los archivos de store completos
- 2-3 componentes refactorizados mostrando el antes/después
- Métricas de rendimiento (menor cantidad de rerenders)
- Recuerda que en Zustand, el estado debe ser siempre inmutable. Usa el operador spread o métodos que retornen nuevos arrays/objects.
- Para selectores que combinan múltiples valores del store, considera crear un hook personalizado que use useMemo internamente para optimización.
- Cuando dividas en múltiples stores, algunos componentes necesitarán acceder a ambos. Puedes crear hooks combinados o simplemente usar múltiples useStore hooks.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.