Gestión de estado en 2026: qué usar y cuándo
Guía práctica para elegir la gestión de estado adecuada en React moderno — de useState a Zustand y React Query — y el razonamiento tras cada opción.

La paradoja de la gestión de estado
El ecosistema nunca ha tenido tantas opciones de gestión de estado, y sin embargo la mayoría de las aplicaciones solo necesita un puñado de ellas. El error que veo con más frecuencia es recurrir a una librería de estado global antes de probar soluciones más simples.
Este es mi marco de decisión, perfeccionado tras trabajar en decenas de aplicaciones React.
La jerarquía del estado
Antes de elegir una herramienta, clasifica lo que estás gestionando:
| Categoría | Ejemplos | Mejor herramienta |
|---|---|---|
| Estado de UI local | modal abierto, campo de formulario, hover | useState / useReducer |
| Estado de UI compartido | pestaña seleccionada, barra lateral abierta | Context o Zustand |
| Caché del servidor | datos de usuario, listado de productos | TanStack Query / SWR |
| Estado en la URL | filtros, paginación, búsqueda | Parámetros de URL / nuqs |
| Preferencias persistentes | tema, idioma | localStorage + context |
La mayoría de las decisiones de "necesitamos Redux" en realidad son decisiones de "necesitamos TanStack Query".
Capa 1: estado local — empieza por aquí
Empieza siempre por el estado local. La gran mayoría del estado de UI pertenece a un único componente.
// ✅ This doesn't need to be global
function Modal({ onClose }: { onClose: () => void }) {
const [step, setStep] = useState<"details" | "confirm" | "done">("details");
const [formData, setFormData] = useState({ name: "", email: "" });
return (
<dialog>
{step === "details" && (
<DetailsForm data={formData} onChange={setFormData} onNext={() => setStep("confirm")} />
)}
{step === "confirm" && (
<ConfirmStep data={formData} onBack={() => setStep("details")} onSubmit={handleSubmit} />
)}
</dialog>
);
}Para estado local complejo con múltiples subvalores, useReducer da como resultado un código más limpio que usar varias llamadas a useState.
Capa 2: estado del servidor — TanStack Query
Si estás guardando datos de una API en un store global, estás yendo a contracorriente. El estado del servidor tiene propiedades particulares: es asíncrono, puede quedar obsoleto y puede ser invalidado por otras operaciones. TanStack Query está diseñado justamente para esto.
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient();
// Automatic loading, error, and staleness handling
const { data: user, isLoading } = useQuery({
queryKey: ["users", userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
});
const updateUser = useMutation({
mutationFn: (data: Partial<User>) => patchUser(userId, data),
onSuccess: () => {
// Invalidate and refetch after mutation
queryClient.invalidateQueries({ queryKey: ["users", userId] });
},
});
if (isLoading) return <Skeleton />;
return (
<form onSubmit={(e) => {
e.preventDefault();
updateUser.mutate({ name: e.target.name.value });
}}>
<input name="name" defaultValue={user.name} />
<button type="submit" disabled={updateUser.isPending}>
{updateUser.isPending ? "Saving..." : "Save"}
</button>
</form>
);
}Esto sustituye al manejo manual del estado de carga, el estado de error, la lógica de caché, el refetching en segundo plano y las actualizaciones optimistas — todo eso que antes vivía en tu store global.
Capa 3: estado en la URL — infrautilizado y subestimado
Los filtros, la paginación, el orden y las búsquedas pertenecen a la URL. Es estado gratuito que:
- Sobrevive a las recargas de página
- Se puede compartir mediante un enlace
- Funciona con los botones de avanzar/retroceder del navegador
- Es indexable por los motores de búsqueda
import { useQueryStates } from "nuqs";
function ProductList() {
const [params, setParams] = useQueryStates({
page: parseAsInteger.withDefault(1),
sort: parseAsString.withDefault("newest"),
category: parseAsString.withDefault(""),
minPrice: parseAsInteger.withDefault(0),
});
const { data } = useQuery({
queryKey: ["products", params],
queryFn: () => fetchProducts(params),
});
return (
<div>
<FilterBar filters={params} onChange={setParams} />
<ProductGrid products={data?.items} />
<Pagination
page={params.page}
total={data?.total}
onChange={(page) => setParams({ page })}
/>
</div>
);
}Capa 4: estado de cliente — Zustand cuando lo necesitas
Cuando tienes un estado que es:
- Compartido entre muchos componentes distantes
- No proviene del servidor
- Difícil de colocar cerca de donde se usa
Zustand es la solución más limpia: mínimo boilerplate, sin necesidad de providers y con excelente soporte de TypeScript.
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface CartStore {
items: CartItem[];
addItem: (product: Product, quantity: number) => void;
removeItem: (productId: string) => void;
clearCart: () => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (product, quantity) =>
set((state) => {
const existing = state.items.find((i) => i.productId === product.id);
if (existing) {
return {
items: state.items.map((i) =>
i.productId === product.id
? { ...i, quantity: i.quantity + quantity }
: i,
),
};
}
return {
items: [
...state.items,
{ productId: product.id, product, quantity },
],
};
}),
removeItem: (productId) =>
set((state) => ({
items: state.items.filter((i) => i.productId !== productId),
})),
clearCart: () => set({ items: [] }),
total: () =>
get().items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0,
),
}),
{ name: "cart-storage" }, // Auto-persisted to localStorage
),
);El árbol de decisión
New state to manage?
│
├─ Is it UI state for one component?
│ └─ YES → useState / useReducer
│
├─ Is it data from a server?
│ └─ YES → TanStack Query
│
├─ Should it be in the URL?
│ └─ YES → URL params (nuqs)
│
├─ Is it shared across many components?
│ └─ YES → Zustand
│
└─ Is it a user preference that should persist?
└─ YES → localStorage + small Zustand slice
El mejor sistema de gestión de estado es el que casi ni notas. Si estás constantemente peleando con tu librería de estado, probablemente esté haciendo más trabajo del que le corresponde, trabajo que otras capas podrían resolver mejor.


