//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Usa funciones selector con useStore(selector) para suscribirte a slices específicos del state. Los componentes solo se re-renderizan cuando cambia el valor seleccionado, evitando renderizados innecesarios.
"use client";
import { useCartStore } from "@/stores/cart-store";
function CartBadge() {
// Solo re-renderiza cuando cambia el array items
const itemCount = useCartStore((state) => state.items.length);
return <span className="badge">{itemCount}</span>;
}
function CartTotal() {
// Solo re-renderiza cuando cambia el total
const total = useCartStore((state) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
return <span>${total.toFixed(2)}</span>;
}// stores/dashboard-store.ts
import { create } from "zustand";
interface DashboardStore {
user: { name: string; avatar: string };
notifications: { id: string; message: string }[];
theme: "light" | "dark";
sidebarOpen: boolean;
toggleSidebar: () => void;
toggleTheme: () => void;
addNotification: (message: string) => void;
}
export const useDashboardStore = create<DashboardStore>((set) => ({
user: { name: "Jane", avatar: "/avatar.png" },
notifications: [],
theme: "light",
sidebarOpen: true,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
toggleTheme: () => set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
addNotification: (message) =>
set((s) => ({
notifications: [...s.notifications, { id: crypto.randomUUID(), message }],
})),
}));// components/header.tsx
"use client";
import { useDashboardStore } from "@/stores/dashboard-store";
import { useShallow } from "zustand/react/shallow";
// Malo: re-renderiza ante CUALQUIER cambio del store
function HeaderBad() {
const store = useDashboardStore();
return <div>{store.user.name}</div>;
}
// Bueno: solo re-renderiza cuando cambia user.name
function HeaderGood() {
const userName = useDashboardStore((s) => s.user.name);
return <div>{userName}</div>;
}
// Bueno: varios valores con comparación superficial
function HeaderWithMultiple() {
const { name, avatar } = useDashboardStore(
useShallow((s) => ({ name: s.user.name, avatar: s.user.avatar }))
);
return (
<div>
<img src={avatar} alt={name} />
<span>{name}</span>
</div>
);
}
// Bueno: seleccionar varios primitivos con array y useShallow
function NotificationBar() {
const [notifications, theme] = useDashboardStore(
useShallow((s) => [s.notifications, s.theme])
);
return (
<div className={theme}>
{notifications.length} notificaciones
</div>
);
}===) por defecto para comparar el valor seleccionado anterior y el siguiente.{} !== {}.useShallow de zustand/react/shallow realiza una comparación superficial sobre el objeto o array seleccionado, evitando re-renderizados cuando las propiedades individuales no han cambiado.Auto-generación de selectores:
import { create } from "zustand";
import { StoreApi, UseBoundStore } from "zustand";
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never;
function createSelectors<S extends UseBoundStore<StoreApi<object>>>(store: S) {
const storeIn = store as WithSelectors<typeof store>;
storeIn.use = {} as any;
for (const key of Object.keys(storeIn.getState())) {
(storeIn.use as any)[key] = () => storeIn((s: any) => s[key]);
}
return storeIn;
}
// Uso
const useCounterStoreBase = create<CounterState>((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}));
export const useCounterStore = createSelectors(useCounterStoreBase);
// Selectores auto-generados
const count = useCounterStore.use.count();
const increment = useCounterStore.use.increment();Selector derivado memorizado:
import { useMemo } from "react";
function ExpensiveList() {
const items = useCartStore((s) => s.items);
// Memorizar cálculo costoso
const sortedItems = useMemo(
() => [...items].sort((a, b) => b.price - a.price),
[items]
);
return <ul>{sortedItems.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
}Seleccionar acciones (referencia estable):
// Las acciones nunca cambian, así que este selector nunca provoca un re-renderizado
const increment = useCounterStore((s) => s.increment);
const reset = useCounterStore((s) => s.reset);useShallow preserva el tipo de retorno del selector.// el resultado está tipado como number
const count = useStore((s: StoreState) => s.count);
// el resultado está tipado como { name: string; email: string }
const user = useStore(
useShallow((s: StoreState) => ({ name: s.user.name, email: s.user.email }))
);useShallow provoca re-renderizados en cada cambio de state: (s) => ({ a: s.a, b: s.b }) crea un objeto nuevo cada vez.useShallow solo compara un nivel de profundidad. Los objetos o arrays anidados siguen usando igualdad por referencia para sus hijos.(s) => s.increment nunca provoca re-renderizados porque las referencias de las funciones son estables en Zustand. Esto es seguro y recomendado.useMemo después de seleccionar los datos sin procesar.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Selectores individuales | Precisos, re-renderizados mínimos | Verboso para muchos valores |
| useShallow | Selección de varios valores, API simple | Solo comparación superficial |
| Selectores auto-generados | Cero boilerplate por acceso a campo | Magia, más difícil de depurar |
| Sin selector (store completo) | Simple | Re-renderiza en cada cambio |
===) para comparar el valor seleccionado anterior y el siguiente.{} !== {}.useShallow cuando tu selector devuelve un objeto o array con varios valores.zustand/react/shallow.import { useShallow } from "zustand/react/shallow";
const { name, avatar } = useStore(
useShallow((s) => ({ name: s.user.name, avatar: s.user.avatar }))
);{} !== {}, Zustand lo interpreta como un valor nuevo y provoca un re-renderizado.useShallow para comparar las propiedades individuales en su lugar.useShallow solo compara un nivel de profundidad.createSelectors genera hooks por campo: useStore.use.count().useMemo para cálculos derivados costosos.const items = useCartStore((s) => s.items);
const sorted = useMemo(() => [...items].sort(...), [items]);if o después de un return condicional.useShallow preserva el tipo de retorno del selector.// el resultado está tipado como number
const count = useStore((s: StoreState) => s.count);type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never;useShallow selecciona varios valores pero añade un paso de comparación superficial.Revisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥