Guía detallada de TanStack Query: consultas, caché, mutaciones y interfaz de usuario optimista
TanStack Query gestiona el estado del servidor como un gerente de restaurante: claves de caché compartidas, controles de actualidad, mutaciones coordinadas, invalidación y actualizaciones optimistas sobre tu cliente HTTP.
El tema de hoy es TanStack Query (anteriormente React Query): la biblioteca que convierte la compleja obtención de estado del servidor en flujos de trabajo predecibles relacionados con el caché, la actualización y las mutaciones. Una analogía con un restaurante de alta gama ayuda a recordar los componentes involucrados: el comedor como interfaz de usuario, la cocina como backend, los camareros como clientes HTTP y el gerente como TanStack Query.
¿Qué es TanStack Query?
En una aplicación típica, la interfaz de usuario solicita datos al backend mediante fetch o Axios. Estos clientes no son muy eficaces a la hora de coordinarse. Si cinco componentes solicitan el mismo menú al mismo tiempo, podrían realizarse cinco viajes separados a la cocina. Si alguien pide la sopa del día y otro cliente lo hace diez segundos después, un camarero inexperto tendría que volver a la cocina. Esto sobrecarga al servidor y ralentiza el servicio en el comedor.
TanStack Query actúa como el maître y gerente del restaurante: coordina la obtención, almacenamiento en caché, sincronización y actualización del estado del servidor para que la cocina no se vea abrumada con preguntas idénticas.
Axios / Fetch vs. TanStack Query
Los principiantes suelen pensar que TanStack Query reemplaza a Axios o a fetch. No es así.
- Fetch y Axios son los camareros. Llevan una solicitud a la cocina y traen una respuesta. No recuerdan viajes anteriores, evalúan la frescura ni coordinan a sus colegas.
- TanStack Query es el gerente. Contrata a los camareros para que realicen las tareas. Recuerda qué ha vuelto, si aún se considera fresco, qué mesas comparten el mismo bloc de notas y cuándo enviar a alguien de vuelta después de que la cocina modifique un plato.
Todavía escribes cuerpos de queryFn que llaman a Axios o fetch. TanStack Query envuelve esas llamadas con claves de caché, deduplicación, reintentos y ganchos de ciclo de vida.
¿Qué es TanStack Query? (mapa de capacidades)
Seis capacidades son las más importantes en el trabajo diario con React:
1. Consultas (useQuery): obtener los datos
Lecturas declarativas identificadas por una queryKey, ejecutadas mediante una queryFn.
2. Caché: el cerebro del gestor
Los resultados permanecen en memoria bajo la clave de la consulta, permitiendo que varios componentes compartan una sola solicitud a la red.
3. Actualidad: staleTime vs gcTime
staleTime determina cuándo los datos en caché se consideran lo suficientemente obsoletos como para volver a obtenerlos. gcTime (recolección de basura) decide cuánto tiempo permanecen las entradas de caché no utilizadas antes de ser eliminadas.
4. Mutaciones (useMutation): cambiar los datos
Las operaciones de escritura—crear, actualizar, eliminar—se ejecutan bajo demanda en lugar de al cargar la página.
5. Invalidación de consultas: eliminar el menú
Después de una mutación exitosa, se marcan como obsoletas las consultas relacionadas para que la interfaz se sincronice nuevamente con el servidor.
6. Actualizaciones optimistas: la experiencia de estrella Michelin
Se actualiza inmediatamente la caché, se revierte en caso de error y finalmente se realiza una sincronización definitiva.
El resto de esta guía explica cada funcionalidad mediante ejemplos relacionados con restaurantes y ejemplos de código concretos.
1. Consultas (useQuery): obtener el menú
Una consulta declara qué se desea obtener y cómo hacerlo:
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
// The waiter function (Axios)
const fetchMenu = async () => {
const response = await axios.get('/api/menu');
return response.data;
};
function MenuComponent() {
// The Manager (TanStack Query) orchestrating the process
const { data: menu, isLoading, isError, error } = useQuery({
queryKey: ['menu'], // The label for this specific data
queryFn: fetchMenu, // The waiter doing the fetching
});
if (isLoading) return <div>Waiter is walking to the kitchen... Loading Menu...</div>;
if (isError) return <div>The kitchen is on fire! Error: {error.message}</div>;
return (
<ul>
{menu.map((item) => (
<li key={item.id}>{item.name} - ${item.price}</li>
))}
</ul>
);
}
La queryKey es el nombre del archivo en la libreta del administrador: ['menu'], ['soup'], ['allergies', tableId]. Las claves idénticas comparten caché y eliminan solicitudes duplicadas en tránsito. La queryFn representa el proceso seguido por el camarero: debe devolver una promesa con los datos.
Mientras se ejecuta la primera solicitud, las marcas isPending o de carga permiten que la interfaz muestre marcadores temporales. Los errores se indican mediante isError y error. Los datos exitosos aparecen en data y permanecen disponibles para todos los componentes que monitorean esa clave.
¿Qué es una condición de carrera?
La analogía del restaurante
Imagínese a dos clientes pidiendo sopa mientras la cocina responde con lentitud. Una respuesta tardía para la mesa A no debe sobrescribir la solicitud más reciente de la mesa B. Sin coordinación, gana aquella promesa que se complete por última vez, incluso si los datos ya están desactualizados.
Cómo ocurre esto en React (useEffect)
La obtención manual de datos con useEffect suele olvidar la lógica de interrupción. Al navegar rápidamente entre páginas, una respuesta antigua puede modificar el estado después de que se haya iniciado una solicitud más reciente.
Cómo TanStack Query soluciona este problema
La biblioteca rastrea las consultas en ejecución por clave, puede cancelarlas mediante AbortSignal cuando está disponible, y asegura que los observadores de la interfaz vean transiciones coherentes en el caché en lugar de situaciones caóticas causadas por llamadas aleatorias a setState.
2. Caché: la libreta del administrador
// Waiter function
const fetchMenu = async () => {
console.log("Waiter is walking to the kitchen!"); // We can track how many times this runs
const response = await axios.get('/api/menu');
return response.data;
};
// Component 1: The Sidebar
function MenuSidebar() {
const { data } = useQuery({ queryKey: ['menu'], queryFn: fetchMenu });
return <div>We have {data?.length} items today!</div>;
}
// Component 2: The Main Display
function MenuMainDisplay() {
const { data } = useQuery({ queryKey: ['menu'], queryFn: fetchMenu });
return <div>{data?.map(item => <p>{item.name}</p>)}</div>;
}
Cuando el primer componente se carga con ['menu'], el administrador envía un proceso de espera. Cuando un segundo componente se carga con la misma clave unos milisegundos después, lee directamente de esa libreta en lugar de realizar nuevamente la consulta. Esa deduplicación es la razón por la cual los paneles de control con muchas tarjetas que comparten consultas de usuario o configuración funcionan rápidamente sin necesidad de almacenes globales personalizados.
El administrador en acción (paso a paso)
- Se carga el componente A → falta en caché → conexión a red.
- Llega la respuesta → escritura en caché → se renderiza A.
- Se carga el componente B con la misma clave → acceso directo al caché → se renderiza B de inmediato.
- Según las reglas de actualidad, puede realizarse una nueva carga en segundo plano más tarde sin bloquear la primera renderización de B.
El estado del servidor debe estar en TanStack Query; el verdadero estado de la interfaz del cliente (modal abierto, pestaña seleccionada) puede permanecer en el estado de React o en un almacén ligero del cliente.
3. Actualidad: configuración de staleTime y gcTime
1. staleTime: ¿sigue siendo precisa esta información?
const { data } = useQuery({
queryKey: ['soup'],
queryFn: fetchSoup,
staleTime: 1000 * 60 * 30, // 30 minutes
});
Con staleTime: 10_000, los datos más recientes, de menos de diez segundos, se consideran actualizados: al volver a montar el componente se reutilizan sin necesidad de volver a cargarlos. Una vez que pasan a ser obsoletos, los observadores pueden activar una carga en segundo plano (al volver a montar, al obtener el foco de la ventana o al reconectarse, dependiendo de los valores predeterminados y opciones). Elige el valor de staleTime en función de la volatilidad del dominio: los menús del día pueden ser cortos; las listas de países, en cambio, pueden ser largas.
2. gcTime: ¿Puedo desechar este dato?
const { data } = useQuery({
queryKey: ['allergies', 'table4'],
queryFn: fetchAllergies,
gcTime: 1000 * 60 * 60 * 24, // Keep in memory for 24 hours
});
gcTime controla cuánto tiempo permanece una entrada en la caché después de que todos los observadores dejen de usarla. Un valor corto de gcTime libera la memoria más rápidamente; uno más largo hace que las visitas posteriores sean instantáneas. No lo confundas con staleTime: los datos obsoletos pueden seguir en la memoria hasta que se realice su recolección automática.
El secreto definitivo: stale-while-revalidate
TanStack Query muestra felizmente datos obsoletos mientras vuelve a cargar en segundo plano. Los usuarios ven de inmediato el menú más reciente conocido; cuando la cocina confirma las actualizaciones, la libreta se actualiza. Ese patrón es la razón por la cual la biblioteca parece más rápida que los indicadores de carga en cada visita.
4. Mutaciones (useMutation): agregar un nuevo plato
¿Qué es una mutación?
Una mutación modifica el estado del servidor: realizar un pedido, editar un perfil, eliminar un comentario.
La analogía del restaurante: realizar un pedido
Los camareros no realizan pedidos automáticamente cuando un cliente se sienta; esperan una solicitud clara. Las mutaciones son lo mismo: se ejecutan cuando se llama a mutate o mutateAsync.
El código: crear el formulario de pedido
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
import { useState } from 'react';
// 1. The Waiter Function (The actual network request)
const placeOrder = async (orderData) => {
// We are using POST because we are creating a new order
const response = await axios.post('/api/orders', orderData);
return response.data;
};
function OrderForm() {
const [dish, setDish] = useState('');
// 2. The Manager orchestrating the mutation
const mutation = useMutation({
mutationFn: placeOrder,
// We can also trigger side effects right here!
onSuccess: (data) => {
console.log("Chef says: Order confirmed!", data);
},
onError: (error) => {
console.log("Chef says: We have a problem.", error.message);
}
});
const handleSubmit = (e) => {
e.preventDefault();
// 3. Triggering the mutation and passing the variables
mutation.mutate({ dishName: dish, tableNumber: 4 });
};
return (
<form onSubmit={handleSubmit}>
<input
value={dish}
onChange={(e) => setDish(e.target.value)}
placeholder="What would you like?"
/>
{/* Notice how we use isPending to disable the button so they don't double-order! */}
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Sending to Kitchen...' : 'Place Order'}
</button>
{/* Handling the feedback */}
{mutation.isError && <p style={{ color: 'red' }}>Failed: {mutation.error.message}</p>}
{mutation.isSuccess && <p style={{ color: 'green' }}>Order placed successfully!</p>}
</form>
);
}
Conecte onSuccess para mostrar comentarios, indicaciones de navegación o mensajes de invalidación. Utilice mutateAsync cuando necesite esperar a que finalice la operación en los controladores de envío.
Detalles avanzados para desarrolladores
1. No se ejecuta automáticamente
A diferencia de las consultas, las mutaciones permanecen inactivas hasta que se invocan, lo que evita escrituras accidentales durante la renderización.
2. isPending vs isLoading
En v5, prefiera isPending para indicar el estado en curso de una mutación. Alinee la desactivación de la interfaz con esa bandera.
3. Evitar el problema de doble clic
Desactive el botón de envío mientras isPending esté en valor verdadero, para que los usuarios no puedan enviar pedidos duplicados.
5. Invalidación de consultas: indicar al administrador que actualice el bloc de notas
El problema: el bloc de notas desactualizado
Después de que un chef agrega un plato, las mesas que aún muestran menús en caché ven la lista de ayer hasta que algo vuelve a cargarla.
La solución: invalidación de consultas
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
function AddDishForm() {
// 1. Get access to the Manager's office
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newDish) => {
const response = await axios.post('/api/menu', newDish);
return response.data;
},
// 2. The magic happens HERE in the onSuccess callback
onSuccess: () => {
// 3. Tell the Manager to rip up the menu notepad
queryClient.invalidateQueries({ queryKey: ['menu'] });
console.log("Menu invalidated! The Manager is getting a fresh copy.");
},
});
// ... form code
}
invalidateQueries marca las entradas correspondientes como obsoletas y provoca que los observadores activos vuelvan a cargar la información.
¿Qué sucede exactamente al llamar a invalidateQueries?
Las consultas correspondientes se vuelven obsoletas; los observadores montados vuelven a cargar la información; las entradas no montadas esperan hasta el próximo montaje (sujeto a gcTime). La cocina sigue siendo la fuente de verdad; se indica al bloc de notas que se actualice.
El concepto avanzado: coincidencia difusa
Invalidación específica:
queryClient.invalidateQueries({ queryKey: ['menu', 'lunch'] });
O invalidar todo un prefijo:
// This rips up the breakfast, lunch, and dinner notepads all at once!
queryClient.invalidateQueries({ queryKey: ['menu'] });
La coincidencia de prefijos difusos arruina las libretas de notas para el desayuno, el almuerzo y la cena cuando se invalida de forma general ['menu']: es poderoso y peligroso. Prefiera la clave más específica que mantenga la interfaz correcta.
La regla de oro de las mutaciones
Cada escritura exitosa debe invalidar las lecturas que dependen de ella o actualizar quirúrgicamente la caché. Dejar las lecturas intactas es cómo mienten las interfaces después de los guardados.
6. Actualizaciones optimistas: la experiencia de estrella Michelin
La analogía: la confianza del gerente
Un gerente de confianza puede anotar la nueva cerveza en la cuenta antes de que la cocina lo confirme; luego borrarla si la fuente está vacía.
Los tres pilares de una actualización optimista
Dentro de useMutation:
onMutate: detener las solicitudes conflictivas, hacer un snapshot de la caché y escribir los datos optimistas de inmediato.
onError: restaurar la instantánea si el servidor la rechaza.onSettled: invalidar (o sincronizar de otro modo) para que la caché coincida con la base de datos, independientemente de si la mutación tuvo éxito o falló.El código: agregar un plato de forma optimista
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
function AddDishForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newDish) => {
const response = await axios.post('/api/menu', newDish);
return response.data;
},
// 1. The millisecond the user clicks submit...
onMutate: async (newDish) => {
// A. Cancel any outgoing refetches so they don't overwrite our optimistic update
await queryClient.cancelQueries({ queryKey: ['menu'] });
// B. Take a snapshot of the current menu (The Eraser Backup)
const previousMenu = queryClient.getQueryData(['menu']);
// C. Optimistically update the Manager's notepad right now!
queryClient.setQueryData(['menu'], (oldMenu = []) => {
// We fake an ID for now, the real ID comes from the database later
return [...oldMenu, { ...newDish, id: Math.random().toString() }];
});
// D. Return the snapshot so onError can use it if things go wrong
return { previousMenu };
},
// 2. If the Kitchen catches on fire...
onError: (err, newDish, context) => {
// Use the eraser! Roll back to the snapshot we saved in onMutate
if (context?.previousMenu) {
queryClient.setQueryData(['menu'], context.previousMenu);
}
console.error("Chef says no! Rolling back.", err);
},
// 3. Always run this at the very end, success or fail...
onSettled: () => {
// Tell the Manager to get the real, final menu from the database
queryClient.invalidateQueries({ queryKey: ['menu'] });
},
});
// ... form code
}
Preste atención a la cancelación de consultas en ejecución, a la estructura de las instantáneas y a los caminos de deshacer cambios. La interfaz de usuario optimista parece instantánea, pero nunca debe dejar a la caché sin respuesta cuando la cocina rechaza la solicitud.
Uniendo los pilares fundamentales
- TanStack Query es un gestor de estado asíncrono, no una herramienta de solicitud. Envuelve a Axios/
fetchpara hacer predecible el caos en la red. - La clave de la consulta lo es todo. Ella determina la deduplicación, el almacenamiento en caché y el intercambio entre componentes.
invalidateQueries o actualizaciones óptimas cuidadosamente diseñadas para que el bloc de notas del cliente esté al día con los datos reales.Valores predeterminados prácticos para aplicaciones reales
Comienza con un valor razonable de staleTime para recursos mayormente estáticos (en minutos) y un valor corto o cero para datos volátiles específicos del usuario. Mantén queryFn como una función pura y que pueda ser interrumpida. Centraliza las claves en funciones de fábrica (menuKeys.list(), menuKeys.detail(id)) para que la invalidación se realice de manera consistente y con tipos definidos. Registra los eventos de caché en el entorno de desarrollo al diagnosticar solicitudes duplicadas. Prefiere la invalidación a soluciones manuales complejas para la caché, a menos que una pantalla realmente necesite funcionalidades óptimas avanzadas.
Modos de fallo comunes
- El uso de claves inestables (nuevos objetos literales en cada renderizado) daña la caché.
- Olvidar la invalidación después de las mutaciones muestra datos fantasma.
- Establecer un
staleTimeinfinito sin una estrategia de mutación congela las interfaces de usuario. - Colocar todas las marcas de estado del cliente en la caché de consultas confunde los límites del estado del servidor.
- La invalidación excesivamente amplia (errores como
queryKey: ['']) vuelve a cargar todo el contenido.
Evita estos problemas y el restaurante funcionará correctamente: los camareros se moverán cuando sea necesario, la libreta del gerente permanecerá coherente y los clientes verán la comida caliente sin tener que mirar la puerta de la cocina cada diez segundos.
Cierre
TanStack Query se distingue por gestionar los ciclos de vida relacionados con el estado del servidor: lecturas, actualizaciones y sincronización, dejando que Axios o fetch se encarguen del transporte de datos. Conoce las opciones básicas (teclas), los ajustes de actualidad (staleTime, gcTime) y los procedimientos de escritura (mutaciones, invalidación, actualizaciones optimistas). Gracias a esto, las aplicaciones React dejan de reinventar los cachés de solicitudes en cada useEffect y comienzan a funcionar como un restaurante bien administrado.
Por qué la metáfora del restaurante sigue siendo útil
Las cascadas de red parecen algo abstracto hasta que uno se imagina a cinco camareros corriendo hacia la misma sopa. La deduplicación es como cuando el encargado levanta la mano: un solo recorrido para atender muchas mesas. El mecanismo de “válido mientras se vuelve a validar” consiste en servir el último menú impreso mientras un empleado verifica el tablón de pedidos. La invalidación ocurre al arrancar las páginas cuando el chef cambia las recetas. Las actualizaciones optimistas implican anotar el pedido del cliente en la cuenta antes de confirmarlo, con un borrador listo para usar. Al capacitar a los empleados novatos, es mejor mostrarles esas situaciones antes de explicar los genericos de TypeScript; así la comprensión es más efectiva.
Integración con enrutadores y autenticación
Las claves deben incluir la identidad del inquilino o usuario cuando los datos no son globales: ['menu', restaurantId] o ['allergies', userId]. Al cerrar la sesión, borre la caché para evitar que las páginas del bloc de notas se filtren entre usuarios diferentes. Con React Router o herramientas similares, active la invalidación en las acciones que ya saben qué recursos han cambiado en lugar de volver a cargar todo con cada navegación.
Estrategias de pruebas
Pruebe por separado los mapeadores queryFn a nivel de unidad. En las pruebas de componentes, envuélvalos con QueryClientProvider utilizando un cliente nuevo y estableciendo retry: false para garantizar determinismo. Verifique que las mutaciones llamen a invalidateQueries con las claves esperadas. Para rutas optimistas, simule errores del servidor y confirme que se realice un rollback al estado anterior. Evite compartir un mismo QueryClient entre pruebas no relacionadas sin restablecerlo primero.
Notas de rendimiento
Las listas largas deben gestionarse mediante paginación o consultas infinitas en lugar de usar una única clave masiva. Los selectores (select) permiten que los componentes supervisen partes específicas sin volver a renderizarse ante campos de caché no relacionados. Asegúrese de que los resultados de queryFn sean serializables y estables. Mida las situaciones de recarga excesiva cuando refetchOnWindowFocus se combina con un staleTime muy corto en paneles de control con alto tráfico; ajuste las configuraciones por consulta en lugar de de forma global.
Enfoque de migración desde useEffect puro
Sustituya los efectos de montaje que establecen pares de carga/error/datos por useQuery. Reemplace los controladores imperativos de tipo POST por useMutation. Elimine las cachés hechas a mano. Mantenga instancias de Axios para interceptores y encabezados de autenticación; páselos a queryFn. La migración es gradual: trabajar pantalla por pantalla reduce de inmediato los errores relacionados con concurrencia.
Lista final de verificación antes de lanzar una función
queryKeyestable y jerárquico.staleTimeexplícito seleccionado para el dominio.- Mutación combinada con invalidación u optimismo.
- La interfaz en espera desactiva los envíos duplicados.
- Mensajes de error o límites configurados adecuadamente.
- Las claves con ámbito de autenticación se eliminan al final de la sesión.
Al cumplir con estos seis puntos, TanStack Query deja de ser “otra biblioteca” y se convierte en el gestor silencioso que necesitaba su sala de estar.
Desglose: carga de un menú en tres componentes
Imagínese un encabezado que muestre la sopa del día, una barra lateral que enumere las especialidades del almuerzo y un panel principal que muestre el menú completo. Sin TanStack Query, cada panel podría crear su propio useEffect e iniciar una solicitud a /api/menu. Con un queryKey: ['menu', restaurantId] compartido, la primera carga realiza la consulta a la red; las demás simplemente leen los datos ya almacenados. Cuando el chef modifica la sopa a través de un formulario administrativo usando useMutation, esto invalida ['menu', restaurantId] y actualiza todos los paneles que aún estén en pantalla. Los clientes nunca verán tres sopas diferentes solo porque tres camareros tuvieran opiniones distintas.
Esa misma estructura incluye funciones de eliminación de duplicados, caché compartido, gestión de mutaciones e invalidación de datos. La mayoría de las pantallas en producción son variaciones de este modelo: encabezado del perfil junto con formulario de configuración, indicador del carrito junto con los artículos del pedido, campana de notificaciones junto con la página de notificaciones.
Diseño de claves de consulta como rutas de archivo
Trate las claves como rutas jerárquicas:
['menu', restaurantId]['menu', restaurantId, 'lunch']['menu', restaurantId, 'item', itemId]['allergies', restaurantId, tableId]
Las fábricas ayudan:
Al invalidar ['menu', restaurantId] se puede realizar una búsqueda aproximada con claves más profundas si está configurado para ello, lo que permite guardar conjuntamente las listas de actualizaciones y las vistas detalladas. Evite incrustar valores no serializables (funciones, instancias de clases) dentro de las claves. Prefiera identificadores primitivos y enums estables.
Elegir staleTime según el idioma del producto
Pregunte a los responsables del producto cuán errónea puede ser la interfaz de usuario durante N segundos. El texto publicitario que cambia mensualmente puede permitir un período largo de vigencia. Los conteos de inventario durante las ventas flash pueden requerir un staleTime cercano a cero, además de una invalidación en cada mutación de compra. Documente la elección junto a la consulta para que los editores futuros no “optimicen” una consulta volátil creando un período largo de obsolescencia.
gcTime es un regulador de memoria. Las aplicaciones móviles con muchas rutas se benefician al conservar brevemente las pantallas recientes para que la navegación hacia atrás parezca instantánea. Los cachés extremadamente grandes en dispositivos con poca memoria necesitan un gcTime más corto o la utilización de paginación.
Mutaciones que parecen seguras
Siempre muestre los estados pendientes y de error. Desactive los botones destructivos mientras haya operaciones en curso. En el caso de las eliminaciones, la eliminación optimista debería restaurar la instantánea si el servidor devuelve 409 o 500. En cuanto a las creaciones, las filas generadas de forma optimista necesitan que los IDs temporales del cliente sean reemplazados por IDs del servidor en caso de éxito; de lo contrario, evite el enfoque optimista e invalide la información cuando el mapeo de IDs sea problemático.
Las mutaciones paralelas en la misma clave pueden entrar en conflicto; colóquelas en cola o desactive los controles correspondientes. La función mutateAsync en las bibliotecas de formularios debe encontrarse dentro de los manejadores de envío con estructuras try/catch, y no en la fase de renderizado.
Patrones de invalidación escalables
Después de iniciar sesión, invalide las claves de ámbito de usuario en lugar de borrar todo el cliente si se debe mantener el contenido público. Después de cerrar sesión, generalmente es correcto utilizar queryClient.clear(). Cuando un WebSocket anuncia que “el menú ha cambiado”, llame a los mismos auxiliares de invalidación que utiliza la mutación HTTP para que ambos métodos compartan una misma estrategia de sincronización.
Realice prefetch al pasar el cursor sobre las páginas de detalles probables: queryClient.prefetchQuery({ queryKey, queryFn }) convierte la latencia percibida en accesos al caché sin modificar el código de la pantalla.
Actualizaciones optimistas sin mitos
El enfoque optimista no es obligatorio para cada solicitud POST. Úselo cuando el camino exitoso sea común, el beneficio para la interfaz sea evidente y sea fácil implementar una reversión. Úselo con moderación cuando la validación en el servidor sea compleja o cuando sea necesario el cuerpo de la respuesta para renderizarla (IDs generados por el servidor, precios, impuestos). Un indicador de carga lento puede ser mejor que mostrar datos incorrectos de inmediato.
Cuando utilice el optimismo, mantenga las capturas inmutables, cancele las consultas conflictivas en onMutate y sincronice siempre en onSettled. Registre los retrocesos en el entorno de desarrollo; los retrocesos silenciosos confunden al equipo de pruebas.
Comparación con los almacenes globales del cliente
Redux o Zustand pueden almacenar datos del servidor, pero será necesario recrear los cachés, solicitar la deduplicación y realizar actualizaciones en segundo plano. TanStack Query se especializa en esa área específica. Mantenga la interfaz temporal en el estado local o en un almacén pequeño del cliente; guarde las entidades del servidor en el caché de consultas. Mezclar estos flujos conduce a fuentes de verdad duplicadas.
Enseñar al equipo
Realice un taller: construya una aplicación de menú sencilla con consulta de lista, consulta de detalles, mutación para crear, invalidación y luego una creación óptimista. Exija el uso de generadores de claves y una función para cerrar la sesión. Una vez que este patrón se convierta en memoria muscular, las aplicaciones más grandes dejarán de acumular errores al usar useEffect para las consultas.
Tabla de resumen en prosa
Las consultas se leen. Las mutaciones se escriben. Las claves nombran las filas del caché. staleTime responde “¿Puedo reutilizarlo sin preguntar a la cocina?”. gcTime responde “¿Puedo descartar esta página del bloc de notas?”. La invalidación indica “la cocina ha cambiado: actualizar”. El optimismo sugiere “actualizar la cuenta ahora y borrarla si es rechazada”. El transporte sigue siendo Axios o fetch. Esa división del trabajo constituye todo el producto.
Cenario de extremo a extremo: tablón de ofertas del almuerzo
Un restaurante inicia su turno de almuerzo. La consulta del tablero de especialidades utiliza queryKey: ['menu', restaurantId, 'lunch'] con un staleTime de dos minutos, ya que los tableros de tiza cambian lentamente durante un turno. El widget de sopa en la cabecera emplea ['menu', restaurantId, 'soup'] con un período de vencimiento de treinta segundos. Ambas funciones queryFn llaman a la misma instancia de Axios con interceptores de autenticación. Cuando un administrador guarda una nueva sopa mediante useMutation, la función onSuccess invalida ambas claves, o bien invalida el prefijo compartido ['menu', restaurantId] si se pretende un matching difuso. Los clientes en cada tableta activa ven las actualizaciones sin necesidad de actualizar manualmente.
Si el formulario del administrador utilizara una función fetch personalizada sin invalidación, las tabletas mostrarían información desactualizada hasta que se volvieran a cargar. Ese error es precisamente el que TanStack Query está diseñado para evitar.
Fábricas de claves de consulta en TypeScript
Centralizar claves:
menuKeys.all(restaurantId)menuKeys.lunch(restaurantId)menuKeys.item(restaurantId, itemId)
Las fábricas evitan errores de escritura y permiten buscar las invalidaciones en el código. Prefiera tuplas de primitivas. Cuando existan filtros, incluya objetos de filtro serializados con un orden de claves estable. Nunca incluya todo el objeto de opciones de props en la clave a menos que esté memorizado y sea serializable.
Opciones de useQuery con las que realmente trabajará
Más allá de queryKey y queryFn: enabled controla las solicitudes hasta que existan los IDs; retry gestiona la política de fallos temporales; refetchOnWindowFocus puede desactivarse en paneles complejos; placeholderData o initialData mantienen estables los diseños; select reduce los datos suscritos para disminuir las actualizaciones. Los valores predeterminados son adecuados al inicio; ajuste según cada consulta cuando los perfiles muestren un exceso de actualizaciones.
Comprendiendo stale-while-revalidate desde la perspectiva de la UX
Mostrar el total del carrito de ayer durante 100 ms mientras se vuelve a obtener la información puede ser inaceptable; mostrar un artículo del centro de ayuda de ayer durante un minuto está bien. Esto debe definirse en staleTime, no mediante flags ad hoc. Un fallo al volver a obtener datos en segundo plano no debe borrar los datos antiguos válidos a menos que se elija explícitamente; los usuarios prefieren información ligeramente desactualizada antes que ver un mensaje de error con indicador de carga cuando están sin conexión.
Mutaciones: anatomía de un envío en formato sólido
Deshabilita el botón cuando haya solicitudes pendientes; muestra los errores en línea provenientes de error; al tener éxito, invalida o actualiza la caché; en caso de resolución, borra el estado local del formulario si es apropiado. Utiliza mutateAsync con estructuras try/catch dentro del manejador de envío de la biblioteca de formularios. No llames a mutate en bucles sin control de concurrencia. Para las subidas, muestra el progreso por separado: TanStack Query rastrea el estado de la mutación, no el progreso en bytes.
Casos relacionados con la granularidad de la invalidación
Muy restrictiva: se actualiza la lista pero se olvidan los detalles → la página de detalles queda desactualizada. Demasiado amplia: cada clave bajo ['menu'] se vuelve a cargar → sobrecarga excesiva. Ajusta la granularidad según las pantallas que puedan mostrar inconsistencias. Si tienes dudas, invalida tanto la lista como el ID del detalle que hayas modificado. El prefijo de invalidación se utiliza para una propagación intencional.
Riesgos de las actualizaciones optimistas
La captura de estado debe realizar una clonación profunda suficiente de la estructura para poder restaurar las listas anidadas. Los IDs temporales del cliente no deben filtrarse al servidor. Si varias mutaciones optimistas se superponen, las operaciones de deshacer pueden anularse entre sí; por lo tanto, se debe serializar la interfaz de usuario para esos flujos. Siempre hay que reconciliarla con los datos reales del servidor al finalizar, incluso en caso de éxito, ya que el servidor podría normalizar campos que no se enviaron.
Modo estricto de React y montaje doble
En el entorno de desarrollo, el Modo Estricto invoca dos veces los efectos. TanStack Query elimina las duplicadas por clave, por lo que no deberías ver llamadas a la red repetidas para la misma clave mientras se procesan. Si ocurre, significa que tu clave es inestable o que existen problemas con la identidad de queryFn que están afectando las suposiciones de eliminación de duplicados. Registra las claves al depurar.
Notas sobre SSR e hidratación
Para Next.js y similares, deshidrata el cliente de consultas en el servidor e hidrátalo en el cliente para que la libreta de notas sobreviva a las navegaciones. Asegúrate de que las funciones queryFn se ejecuten en ambos entornos o proporciona un precarga del servidor que llene el caché antes de la renderización. Las diferencias en la estructura de los datos entre el servidor y el cliente causan advertencias de hidratación que parecen errores del framework, pero en realidad son errores relacionados con el inicializado del caché.
Comparación con patrones SWR desarrollados manualmente
Muchos equipos reinventan subconjuntos de esta biblioteca: mapas de caché, recarga automática al enfocar el elemento, mutación y revalidación. TanStack Query estandariza esas soluciones mediante valores predeterminados de la comunidad y herramientas de desarrollo. Desarrollar estas funcionalidades manualmente solo tiene sentido para aplicaciones muy pequeñas o entornos de ejecución exóticos. De lo contrario, la ventaja radica en las horas que se ahorran al no tener que crearlo todo desde cero.
Herramientas de desarrollo y capacidad de observación
Los Devtools de React Query muestran las claves, el grado de obsolescencia, los observadores y el estado de las solicitudes. Enseñe al equipo a leerlos antes de agregar registros en la consola. En entornos de producción, elimine los datos sensibles de los informes de errores; registre las fallas de las consultas con los nombres de las claves, y no con los datos completos, cuando así lo exija la privacidad.
Lista de verificación de antipatrones
Claves inestables; falta de invalidación; estado de obsolescencia infinito sin sincronización de mutaciones; almacenar indicadores de la interfaz en la caché del servidor; invalidación excesivamente amplia; actualizaciones optimistas sin posibilidad de deshacer cambios; ignorar el parámetro enabled hasta que existan los IDs; usar mutaciones para operaciones de lectura. Evite estos errores y el entorno permanecerá ordenado.
Resumen para quienes solo leen rápidamente
Los camareros realizan el transporte. Los gerentes recuerdan y coordinan. Las claves aparecen en las páginas del bloc de notas. Los reguladores de frescura controlan la reutilización. Las mutaciones se escriben. La invalidación y el optimismo mantienen honesto el bloc de notas. Eso es TanStack Query en una sola frase, y la razón por la que se utiliza junto a Axios en lugar de reemplazarlo.
Ejercicios adicionales para la cocina con el fin de practicar
Reconstruye una aplicación pequeña: consulta de lista, consulta de detalles, crea una mutación con invalidación, y luego una creación optimista con un camino de error forzado. Añade una función de cierre de sesión que limpie los datos del cliente. Agrega una carga previa al pasar el cursor sobre los elementos de la lista. Mide las llamadas a la red en Devtools antes y después de usar claves compartidas. Estos ejercicios ayudan a comprender mejor la biblioteca que leer solamente las tablas de API.
Al revisar los PR, pregúntate: ¿cuál es la clave? ¿Qué es staleTime y por qué existe? ¿Qué se invalida después de las escrituras? ¿La interfaz en estado pendiente impide los envíos duplicados? Si las respuestas son claras, la función funcionará correctamente bajo estrés de concurrencia y navegación.
Patrones de precarga que parecen instantáneos
Precargue al pasar el cursor sobre una ruta, al enfocar una pestaña antes de que el usuario haga clic, o después del inicio de sesión para la consulta predeterminada del panel de control. La precarga llena el caché sin necesidad de establecer un observador. Cuando el usuario navega, useQuery encuentra los datos ya cargados y omite la carga del esqueleto. Si precarga con la clave incorrecta, desperdicia ancho de banda; si lo hace con la clave correcta, el rendimiento percibido mejora sin cambiar el código de queryFn.
Combine la precarga con un valor realista para staleTime. Precargar datos que se vuelven obsoletos de inmediato desencadena una nueva carga en segundo plano al instante; sigue siendo mejor que un inicio desde cero, pero no es gratuito. Precargue las consultas del camino crítico; deje en paz las pantallas de configuración poco utilizadas.
Consultas dependientes y control en cascada
Cuando el detalle necesita un ID de una selección de lista, utilice una compuerta con enabled: !!selectedId. Cuando una segunda consulta necesita datos de la primera, encadenelas con cuidado: o anide la segunda clave con el ID del primer resultado, o utilice una única función queryFn que devuelva ambas formas si la API lo permite. Las consultas en secuencia afectan negativamente el TTI; son preferibles las consultas paralelas con encabezados de autenticación compartidos cuando la independencia lo permite.
El modo Suspense modifica cómo se componen los límites de carga. Si el equipo utiliza Suspense, alinee los límites de error y asegúrese de que los errores de consulta se produzcan como se espera. Combinar Suspense con indicadores de carga clásicos confunde a los revisores; elija un estilo por cada árbol de rutas.
Paginación, consultas infinitas y páginas en caché
Las páginas de lista suelen utilizar parámetros de página en la clave: ['orders', { page, pageSize, status }]. Al cambiar de página se crea una nueva entrada en el caché; mantenga los datos anteriores con placeholderData: keepPreviousData (o el equivalente actual de la API) para que la tabla no se muestre vacía. Las consultas infinitas añaden páginas; invalide cuidadosamente para no borrar inesperadamente la posición de desplazamiento. Cuando una mutación edita una fila, modifique esa entrada de página o invalide toda la lista según la sensibilidad al orden de clasificación.
Recuperación de errores y experiencia de usuario al intentar nuevamente
Las reintentos predeterminados ayudan con las redes móviles inestables. Para códigos 401/403, desactive los reintentos y redirija al proceso de inicio de sesión. En el caso del código 404 en las páginas detalladas, genere un error de inmediato. Muestre failureCount y failureReason en las pantallas de soporte para usuarios avanzados. Los receptores globales de QueryCache pueden mostrar notificaciones en caso de error una sola vez por clave, en lugar de una vez por observador, evitando así oleadas de notificaciones cuando cinco componentes comparten una consulta fallida.
Estrategias de pruebas
En las pruebas unitarias, utilice un QueryClient nuevo con la opción retry: false y una recopilación de basura breve. Simule queryFn o utilice MSW. Verifique los estados de carga, éxito y error. En el caso de mutaciones, asegúrese de que se haya llamado a invalidateQueries con la clave esperada. Las pruebas de integración deben confirmar que dos componentes que comparten una clave no realicen consultas duplicadas. Las pruebas inestables suelen deberse a caché residual entre casos; cree un cliente nuevo para cada prueba.
Actualizaciones de versión y desviación de la API
TanStack Query de v4 a v5 cambió el nombre de algunas opciones y modificó los valores predeterminados. Al actualizar, lea la guía de migración, actualice el paquete Devtools y vuelva a revisar el uso de keepPreviousData / placeholderData. Fije las versiones en los archivos de bloqueo. Considere los cambios en la estructura de las claves de consulta como problemas graves: los cachés antiguos pueden no coincidir con las nuevas claves después del despliegue; acepte un caché frío temporal o versione el prefijo de la clave.
Notas de campo de incidentes en producción
Un equipo detectó envíos duplicados porque el botón de envío permaneció habilitado mientras isPending era verdadero en otra instancia de mutación. Otro borró todo el caché al cerrar sesión de forma incorrecta, al crear un nuevo cliente sin eliminar la referencia al proveedor anterior. Un tercero codificó los objetos de usuario en claves y dañó la posibilidad de compartir estructuras. Escriban estos casos en los documentos de inducción para que los nuevos integrantes hereden estas dificultades sin tener que descubrirlas de nuevo.
Documenten los valores predeterminados de su sistema: el valor por defecto de staleTime, qué consultas son específicas del usuario, cómo se limpia el estado al cerrar sesión y cuándo se permiten actualizaciones optimistas. La consistencia supera a la astucia.
Notas de campo de incidentes en producción
Un equipo detectó envíos duplicados porque el botón de envío permaneció habilitado mientras isPending era verdadero en otra instancia de mutación. Otro borró todo el caché al cerrar sesión de forma incorrecta, al crear un nuevo cliente sin eliminar la referencia al proveedor anterior. Un tercero codificó los objetos de usuario en claves y dañó la posibilidad de compartir estructuras. Escriban estos casos en los documentos de inducción para que los nuevos integrantes hereden estas dificultades sin tener que descubrirlas de nuevo.
Documenten los valores predeterminados de su sistema: el valor por defecto de staleTime, qué consultas son específicas del usuario, cómo se limpia el estado al cerrar sesión y cuándo se permiten actualizaciones optimistas. La consistencia supera a la astucia.
Notas de campo de incidentes en producción
Un equipo detectó envíos duplicados porque el botón de envío permaneció habilitado mientras isPending era verdadero en otra instancia de mutación. Otro borró todo el caché al cerrar sesión de forma incorrecta, al crear un nuevo cliente sin eliminar la referencia al proveedor anterior. Un tercero codificó los objetos de usuario en claves y dañó la posibilidad de compartir estructuras. Escriban estas historias en los documentos de inducción para que los nuevos integrantes hereden estos problemas sin tener que descubrirlos nuevamente.
Documenten los valores predeterminados de su sistema: el valor por defecto de staleTime, qué consultas son específicas del usuario, cómo se limpia el estado al cerrar sesión y cuándo se permiten actualizaciones optimistas. La consistencia supera a la astucia.
Lecturas relacionadas
- TanStack Query para React: Caché, Refetching y Mutaciones — Reemplace el código base de useEffect fetch por TanStack Query: claves de consulta, staleTime, gcTime, mutaciones y cuándo la biblioteca resulta innecesaria.
- TanStack Query en 2026: Adaptador Lit, Solid v6 beta y claves de consulta estables — Novedades en los adaptadores de TanStack Query: soporte para Lit, Solid Query v6 beta, herramientas de desarrollo más avanzadas y tipado con persister, además de por qué las claves de consulta estables siguen siendo importantes.