¡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


2. 🛠️ Configuración Inicial

  1. Instalación:

    npm install @tanstack/react-query @tanstack/react-query-devtools
    # o
    yarn add @tanstack/react-query @tanstack/react-query-devtools
  2. 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 () =&gt; {       // **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 () =&gt; {
  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) =&gt; (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

Opciones Comunes de useQuery


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) =&gt; { // **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) =&gt; {
    // 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) =&gt; {
    // Se ejecuta si la mutación falla
  },
  onSettled: (data, error, variables, context) =&gt; {
    // Se ejecuta siempre, haya éxito o fallo
  },
  onMutate: async (variables) =&gt; {
    // 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) =&gt; {
  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: () =&gt; {
      // Invalida la query 'todos' para que se refetchee automáticamente
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  const handleSubmit = (e) =&gt; {
    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


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):

  1. 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.
  2. onError: Si la mutación falla.
    • Usa el context (datos antiguos guardados) para revertir el caché a su estado previo.
  3. 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 }) =&gt; {
  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) =&gt; {
      // 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) =&gt;
        old ? old.map((t) =&gt; (t.id === newTodo.id ? { ...t, ...newTodo } : t)) : []
      );

      return { previousTodos }; // Este 'context' se pasa a onError y onSettled
    },
    onError: (err, newTodo, context) =&gt; {
      // 4. Si falla, revierte el caché a los datos anteriores
      queryClient.setQueryData(['todos'], context.previousTodos);
      alert('Error al actualizar: ' + err.message);
    },
    onSettled: () =&gt; {
      // 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={() =&gt; 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 }) =&gt; { // pageParam es el valor devuelto por getNextPageParam
    const res = await fetch(`/api/posts?cursor=${pageParam}`);
    return res.json();
  },
  getNextPageParam: (lastPage, allPages) =&gt; {
    // 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 }) =&gt; {
  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) =&gt; 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) =&gt; (
        <React.Fragment key={i}>
          {page.posts.map((post) =&gt; (
            <p key={post.id}>{post.title}</p>
          ))}
        </React.Fragment>
      ))}
      <button
        onClick={() =&gt; 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


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.