¡Excelente iniciativa! Un “cheatsheet” completo y bien estructurado de TanStack Query (anteriormente React Query) es una herramienta invaluable. He preparado uno que es conciso pero cubre los aspectos más importantes, con ejemplos claros y puntos clave.
Este cheatsheet está diseñado para ser entregado a una IA conversacional, por lo que es altamente estructurado, con ejemplos de código claros y explicaciones directas para facilitar su procesamiento y uso en la generación de respuestas precisas sobre TanStack Query.
🚀 TanStack Query (React Query) Cheatsheet Completo 🚀
TanStack Query (anteriormente React Query) es una poderosa librería para la gestión, el caching y la sincronización de datos asíncronos en aplicaciones React (y otras con TanStack Query). Elimina gran parte del trabajo repetitivo y las complejidades de la gestión de estados de servidor, mejorando drásticamente la experiencia del desarrollador y el usuario.
1. 🌟 Conceptos Clave
QueryClient: El “cerebro” de TanStack Query. Contiene el caché de todas tus queries y mutaciones.QueryClientProvider: El componente React que envuelve tu aplicación, proveyendo la instancia deQueryClienta todos tus componentes.Query Key: Un array único que identifica tus datos en el caché. ¡Es fundamental! Cuando los datos cambian, se invalidan o se refetchan usando suQuery Key.- Ej:
['todos'],['todo', todoId],['posts', { type: 'draft', page: 1 }]
- Ej:
Query: Una operación para leer datos del servidor (GET). Los datos se cachean y se gestionan automáticamente.Mutation: Una operación para escribir/modificar datos en el servidor (POST, PUT, DELETE). No se cachean automáticamente, pero pueden invalidar o actualizar queries existentes.- Stale-While-Revalidate: El modelo principal de TanStack Query. Los datos son “stale” (obsoletos) por defecto después de un tiempo, lo que activa refetches en segundo plano para mantener los datos frescos.
2. 🛠️ Configuración Inicial
-
Instalación:
npm install @tanstack/react-query @tanstack/react-query-devtools # o yarn add @tanstack/react-query @tanstack/react-query-devtools -
Configuración del
QueryClientProvider:// src/main.jsx o src/App.jsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.jsx'; import './index.css'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // Opcional pero muy útil // Crea una instancia de QueryClient const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutos por defecto para datos "stale" cacheTime: 1000 * 60 * 10, // 10 minutos por defecto para que los datos permanezcan en caché refetchOnWindowFocus: true, // Refrescar al enfocar la ventana por defecto retry: 3, // Reintentar 3 veces en caso de fallo }, }, }); ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> <ReactQueryDevtools initialIsOpen={false} /> {/* Devtools */} </QueryClientProvider> </React.StrictMode>, );
3. 🔍 Queries (Lectura de Datos)
useQuery - El Hook Principal
Hook para fetching, caching y sincronización de datos.
Sintaxis Básica:
const {
data, // Datos obtenidos (undefined inicialmente)
isLoading, // true mientras se carga por primera vez
isError, // true si la query falló
error, // Objeto de error si isError es true
isSuccess, // true si la query fue exitosa y tiene data
isFetching, // true mientras se está realizando cualquier fetch (inicial, refetch, background)
status, // 'loading', 'error', 'success'
refetch, // Función para forzar un refetch
// ...otras propiedades
} = useQuery({
queryKey: ['mi-query-key'], // **ARRAY OBLIGATORIO** para identificar la query
queryFn: async () => { // **FUNCIÓN ASÍNCRONA OBLIGATORIA** que devuelve una Promise
const res = await fetch('/api/mis-datos');
if (!res.ok) {
throw new Error('Error al obtener datos');
}
return res.json();
},
// ...opciones adicionales
});
Ejemplo de Uso:
import { useQuery } from '@tanstack/react-query';
const fetchTodos = async () => {
const res = await fetch('/api/todos');
if (!res.ok) throw new Error('Failed to fetch todos');
return res.json();
};
function TodoList() {
const { data: todos, isLoading, isError, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
if (isLoading) return <div>Cargando tareas...</div>;
if (isError) return <div>Error: {error.message}</div>;
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
Opciones Comunes de useQuery
staleTime:number | Infinity(ms)- Tiempo que los datos permanecen “frescos” (no obsoletos). Durante este tiempo, no se refetchean en segundo plano al montar el componente o al enfocar la ventana.
0(por defecto): Los datos se consideranstaleinmediatamente.Infinity: Los datos nunca se consideranstale(útil para datos estáticos).
cacheTime:number | Infinity(ms)- Tiempo que los datos permanecen en el caché después de que el último observador de la query se desmonte. Después de este tiempo, los datos son “garbage collected”.
5 * 60 * 1000(5 minutos por defecto).Infinity: Los datos permanecen en caché indefinidamente.
enabled:boolean- Si es
false, la query no se ejecutará automáticamente. Útil para “queries dependientes”. - Ej:
enabled: !!userId(solo se ejecuta siuserIdexiste).
- Si es
refetchOnWindowFocus:boolean | 'always'true(por defecto): Refetch al enfocar la ventana.false: Desactiva esta opción.
refetchOnMount:boolean | 'always'true(por defecto): Refetch al montar el componente si los datos sonstale.
refetchOnReconnect:boolean | 'always'true(por defecto): Refetch al reconectar la red.
retry:boolean | numbertrue(por defecto): Reintentar 3 veces en caso de fallo.false: No reintentar.number: Número de reintentos.
select:(data: TData) => TSelectData- Función para transformar o seleccionar una parte de los datos antes de que se devuelvan al componente.
-
useQuery({ queryKey: ['user', userId], queryFn: fetchUser, select: (data) => data.profile.name, // Solo devuelve el nombre });
initialData:TData | () => TData- Provee los datos iniciales para una query. No se cachean y se reemplazan por los datos reales una vez que se completa el fetch.
- Útil para datos disponibles en el servidor (SSR) o de un cache preexistente.
placeholderData:TData | () => TData | KeepPreviousData- Similar a
initialData, peroplaceholderDatano se almacena en el caché de la query. - Cuando la query está
loadingy no hay datos en caché, se mostrará esteplaceholderData. - Muy útil para UX al pre-renderizar con datos dummy.
keepPreviousData(desde v4): Mantiene los datos de la query anterior mientras se carga la nueva, eliminando estados de carga vacíos.
- Similar a
4. ✍️ Mutations (Escritura de Datos)
useMutation - El Hook de Modificación
Hook para enviar datos al servidor. No cachea los resultados, pero es clave para invalidar y actualizar caches.
Sintaxis Básica:
const {
mutate, // Función para ejecutar la mutación (asíncrona)
mutateAsync, // Versión async/await de mutate
data, // Datos devueltos por la mutación
isLoading, // true mientras la mutación está en progreso
isError, // true si la mutación falló
error, // Objeto de error
isSuccess, // true si la mutación fue exitosa
status, // 'idle', 'loading', 'error', 'success'
// ...otras propiedades
} = useMutation({
mutationFn: async (variables) => { // **FUNCIÓN ASÍNCRONA OBLIGATORIA**
const res = await fetch('/api/crear-item', {
method: 'POST',
body: JSON.stringify(variables),
headers: { 'Content-Type': 'application/json' },
});
if (!res.ok) throw new Error('Fallo al crear item');
return res.json();
},
// Callbacks para gestionar el estado
onSuccess: (data, variables, context) => {
// Se ejecuta al éxito de la mutación
// `data`: resultado de la mutación
// `variables`: variables pasadas a `mutate`
// `context`: valor devuelto por `onMutate`
},
onError: (error, variables, context) => {
// Se ejecuta si la mutación falla
},
onSettled: (data, error, variables, context) => {
// Se ejecuta siempre, haya éxito o fallo
},
onMutate: async (variables) => {
// Se ejecuta ANTES de que la mutación se envíe.
// Útil para actualizaciones optimistas.
// Debe devolver un 'context' que se pasa a onError/onSettled.
},
});
Ejemplo de Uso (Crear Tarea):
import { useMutation, useQueryClient } from '@tanstack/react-query';
const addTodo = async (newTodo) => {
const res = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTodo),
});
if (!res.ok) throw new Error('Failed to add todo');
return res.json();
};
function AddTodoForm() {
const queryClient = useQueryClient();
const { mutate, isLoading } = useMutation({
mutationFn: addTodo,
onSuccess: () => {
// Invalida la query 'todos' para que se refetchee automáticamente
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
const handleSubmit = (e) => {
e.preventDefault();
const title = e.target.elements.title.value;
mutate({ title, completed: false });
};
return (
<form onSubmit={handleSubmit}>
<input name="title" placeholder="Nueva tarea" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Agregando...' : 'Agregar Tarea'}
</button>
</form>
);
}
5. 🔄 Gestión del Caché (useQueryClient)
useQueryClient Hook
Obtiene la instancia del QueryClient.
import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
// ...
}
Métodos Clave de queryClient
queryClient.invalidateQueries(options):- Marca las queries como
staley las refetchea en segundo plano. ¡Es la forma más común de mantener los datos frescos! -
queryClient.invalidateQueries({ queryKey: ['todos'] }); // Invalida todas las queries con key ['todos'] queryClient.invalidateQueries({ queryKey: ['posts', { authorId: 123 }] }); // Invalida la query específica queryClient.invalidateQueries({ queryKey: ['posts'], exact: true }); // Solo 'posts', no 'posts', 1 queryClient.invalidateQueries({ queryKey: ['todos'], refetchType: 'none' }); // Invalida pero no refetchea inmediatamente queryClient.invalidateQueries({ queryKey: ['todos'], refetchType: 'active' }); // Solo queries activas
- Marca las queries como
queryClient.setQueryData(queryKey, updater, options):- Actualiza manualmente los datos de una query en el caché.
-
// Añadir un nuevo todo al cache directamente queryClient.setQueryData(['todos'], (oldTodos) => [...oldTodos, newTodo]); // O actualizar un todo existente queryClient.setQueryData(['todo', todoId], (oldTodo) => ({ ...oldTodo, completed: !oldTodo.completed }));
queryClient.getQueryData(queryKey):- Lee directamente los datos del caché para una
queryKeydada. No activa refetch. -
const todos = queryClient.getQueryData(['todos']);
- Lee directamente los datos del caché para una
queryClient.refetchQueries(options):- Fuerza un refetch de las queries coincidentes. Equivalente a
invalidateQueriessin marcarlas comostaleprimero. -
queryClient.refetchQueries({ queryKey: ['todos'] });
- Fuerza un refetch de las queries coincidentes. Equivalente a
queryClient.removeQueries(options):- Elimina queries del caché.
6. ✨ Actualizaciones Optimistas
Una técnica UX poderosa donde se actualiza la UI inmediatamente después de una mutación, asumiendo que tendrá éxito. Si falla, se revierte.
Proceso Clave (onMutate, onError, onSettled):
onMutate: Se ejecuta antes de enviar la mutación.- Cancela cualquier refetch pendiente para la misma query.
- Guarda una copia de los datos actuales del caché (
context). - Actualiza manualmente el caché con el nuevo estado optimista.
onError: Si la mutación falla.- Usa el
context(datos antiguos guardados) para revertir el caché a su estado previo.
- Usa el
onSettled: (Siempre se ejecuta, éxito o fallo).- Invalida la query para forzar un refetch y obtener el estado real del servidor, asegurando la consistencia.
Ejemplo (Toggle Todo completed status):
import { useMutation, useQueryClient } from '@tanstack/react-query';
const updateTodoStatus = async ({ id, completed }) => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed }),
});
if (!res.ok) throw new Error('Failed to update todo');
return res.json();
};
function TodoItem({ todo }) {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: updateTodoStatus,
onMutate: async (newTodo) => {
// 1. Cancela cualquier refetch pendiente de la query de todos
await queryClient.cancelQueries({ queryKey: ['todos'] });
// 2. Guarda el valor anterior (context)
const previousTodos = queryClient.getQueryData(['todos']);
// 3. Actualiza optimísticamente el caché
queryClient.setQueryData(['todos'], (old) =>
old ? old.map((t) => (t.id === newTodo.id ? { ...t, ...newTodo } : t)) : []
);
return { previousTodos }; // Este 'context' se pasa a onError y onSettled
},
onError: (err, newTodo, context) => {
// 4. Si falla, revierte el caché a los datos anteriores
queryClient.setQueryData(['todos'], context.previousTodos);
alert('Error al actualizar: ' + err.message);
},
onSettled: () => {
// 5. Siempre, refetchea la query para asegurar la consistencia del servidor
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
return (
<li
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => mutate({ id: todo.id, completed: !todo.completed })}
>
{todo.title}
</li>
);
}
7. ♾️ Infinite Queries (useInfiniteQuery)
Para listas que se cargan “infinitamente” (ej: feeds de Instagram, paginación con “Cargar Más”).
const {
data, // Contiene `pages` (array de arrays de datos) y `pageParams`
fetchNextPage, // Función para cargar la siguiente página
hasNextPage, // true si hay más páginas para cargar
isFetchingNextPage, // true mientras se carga la siguiente página
// ...otras propiedades de useQuery
} = useInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: async ({ pageParam = 0 }) => { // pageParam es el valor devuelto por getNextPageParam
const res = await fetch(`/api/posts?cursor=${pageParam}`);
return res.json();
},
getNextPageParam: (lastPage, allPages) => {
// Retorna el cursor para la siguiente página
// Retorna `undefined` si no hay más páginas
return lastPage.nextCursor ?? undefined;
},
initialPageParam: 0, // El valor inicial para pageParam en la primera llamada
});
Ejemplo de Uso:
import { useInfiniteQuery } from '@tanstack/react-query';
const fetchPosts = async ({ pageParam = 0 }) => {
const res = await fetch(`/api/posts?cursor=${pageParam}`);
return res.json();
};
function InfinitePostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
error,
} = useInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: fetchPosts,
getNextPageParam: (lastPage) => lastPage.nextCursor, // Asume que la API devuelve { posts: [], nextCursor: '...' }
initialPageParam: 0,
});
if (isLoading) return <div>Cargando posts...</div>;
if (isError) return <div>Error: {error.message}</div>;
return (
<div>
{data.pages.map((page, i) => (
<React.Fragment key={i}>
{page.posts.map((post) => (
<p key={post.id}>{post.title}</p>
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Cargando más...'
: hasNextPage
? 'Cargar más'
: 'No hay más posts'}
</button>
</div>
);
}
8. 🛠️ Devtools
Esencial para depuración. Permite visualizar el caché, el estado de las queries, mutaciones, etc.
// En tu archivo raíz (main.jsx / App.jsx)
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
// Dentro de QueryClientProvider
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} /> {/* initialIsOpen controla si se abre por defecto */}
</QueryClientProvider>
9. 💡 Buenas Prácticas y Consejos
- Organiza tus Query Keys: Mantén una estructura lógica y consistente para tus
queryKeys.- Ej:
['todos'],['todos', todoId],['users', userId, 'posts'].
- Ej:
- Abstrae tus
queryFns: Separa la lógica de fetching de tus componentes.-
// api/todos.js export const fetchTodos = async () => {...}; export const createTodo = async (newTodo) => {...}; // Componente import { fetchTodos } from './api/todos'; useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
-
- Invalidación Granular: Invalida solo las queries necesarias para minimizar refetches innecesarios.
- Usa
selectpara transformaciones pequeñas: Si solo necesitas una parte de los datos o quieres reformatearlos,selectes eficiente. - Actualizaciones Optimistas con Cuidado: Son geniales para UX, pero maneja los errores y las reversiones correctamente.
- Utiliza
staleTimeycacheTime: Ajusta estos valores a las necesidades de tu aplicación para optimizar el rendimiento y la frescura de los datos. - Observa los Devtools: ¡Son tu mejor amigo para entender qué está pasando con tus datos!
Este cheatsheet te proporciona una base sólida para trabajar con TanStack Query. Cubre los conceptos esenciales, los hooks clave, las técnicas avanzadas como las actualizaciones optimistas y las consultas infinitas, y consejos prácticos para su uso eficiente.