Server State

TanStack Query

TanStack Query es la capa de obtención de datos que le faltaba a React. Convierte los datos del servidor en un caché donde la carga, los errores, el refetching y la invalidación se gestionan automáticamente.

intermediate14 min readUpdated 15 sept 2026
usePosts.ts
tsx
// usePosts.ts
import { useQuery } from "@tanstack/react-query";

async function fetchPosts() {
  const res = await fetch("/api/posts");
  if (!res.ok) throw new Error("Failed to fetch posts");
  return res.json();
}

export function usePosts() {
  return useQuery({
    queryKey: ["posts"],
    queryFn: fetchPosts,
  });
}
Anteriormente
React Query
Framework
React, Vue, Svelte, Solid
Lectura con
useQuery
Escritura con
useMutation
Clave de caché
queryKey
Devtools
Panel dedicado

Por que importa

Por qué el server state necesita su propia herramienta

Un caché real para datos del servidor

Las peticiones se almacenan en caché, se deduplican y se comparten entre componentes, por lo que los mismos datos se obtienen una sola vez.

Carga y errores integrados

Cada query expone el estado, la carga, el error y los datos, para que dejes de escribir manualmente esas banderas.

Actualización por política

El stale time, el refetch al enfocar la ventana y las actualizaciones en segundo plano mantienen los datos al día sin trabajo manual constante.

La imagen completa

Las tres ideas detrás de TanStack Query

Un caché identificado por query, una función de query que obtiene los datos y una invalidación que mantiene el caché actualizado.

Queries

Lectura

Una query key más una función de fetch describen una pieza de datos del servidor y su entrada en el caché.

Mutations

Escritura

useMutation gestiona las peticiones de creación, actualización y eliminación, y puede actualizar el caché al tener éxito.

Invalidación

Sincronización

Marca los datos almacenados como obsoletos (stale) para que se vuelvan a obtener, manteniendo la UI consistente tras un cambio.

TanStack Query de un vistazo

El núcleo de TanStack Query

useQuery

Suscríbete a una pieza de datos del servidor almacenada en caché mediante una clave.

useMutation

Envía peticiones de creación, actualización y eliminación con estados de carga y error.

queryKey

La identidad de una entrada de caché, utilizada para leerla, actualizarla e invalidarla.

staleTime

Cuánto tiempo se consideran los datos como frescos antes de realizar un refetch en segundo plano.

Invalidación

Marca las queries como obsoletas para que se vuelvan a obtener cuando sea necesario.

Devtools

Inspecciona cada query, su estado y el caché en un panel dedicado.

Una breve historia

El auge de la gestión del server-state

  1. 2019

    Lanzamiento de React Query

    Una pequeña librería aporta caching y gestión de server-state a la obtención de datos en React.

    19
  2. 2021

    React Query 3

    Una adopción más amplia y una API madura lo convierten en un estándar común.

    21
  3. 2022

    TanStack Query

    La librería cambia de nombre y se expande a Vue, Svelte y Solid.

    22
  4. 2023

    TanStack Query 5

    Una API más sencilla, mejor inferencia de TypeScript y un bundle más pequeño.

    23
  5. Hoy

    El estándar para server state

    Una capa ampliamente utilizada que se complementa con cualquier librería de client-state.

    Hoy

La guia completa

TanStack Query: Todo lo que necesitas saber

¿Qué es TanStack Query?

TanStack Query, anteriormente conocido como React Query, es una librería de gestión de estado del servidor. Parte de la premisa de que los datos obtenidos de una API son fundamentalmente diferentes del estado que mantienes en useState. Los datos del servidor son compartidos, se almacenan en caché, pueden quedar obsoletos y necesitan ser vueltos a solicitar. Gestionar esto mediante efectos y banderas es tedioso y propenso a errores.

TanStack Query proporciona una caché adecuada para los datos del servidor. Tú describes cómo obtener una pieza de datos y le asignas una clave, y la librería se encarga de la caché, la deduplicación, los estados de carga y error, la actualización en segundo plano y la invalidación. Es la capa de datos que le faltaba a React y se complementa con cualquier librería de estado del cliente.

Queries

Una query se define mediante una key y una función de query. La key identifica la entrada en la caché; la función se encarga de obtener los datos.

// api.ts
async function fetchPost(id: string) {
  const res = await fetch(`/api/posts/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
// Post.tsx
import { useQuery } from "@tanstack/react-query";

export function Post({ id }: { id: string }) {
  const { data, isLoading, isError, error } = useQuery({
    queryKey: ["post", id],
    queryFn: () => fetchPost(id),
  });

  if (isLoading) return <p>Loading…</p>;
  if (isError) return <p>Error: {error.message}</p>;

  return <article>{data.title}</article>;
}

Cada componente que utilice la misma key comparte una única entrada de caché, por lo que montar el componente dos veces solo dispara una petición. Se proporcionan flags de estado, así que nunca tienes que escribirlos tú mismo.

Claves de consulta y la caché

La clave es la identidad de la entrada de la caché y debe incluir cada variable de la que dependa la petición.

// keys.ts
useQuery({ queryKey: ["posts"], queryFn: fetchPosts });
useQuery({ queryKey: ["post", id], queryFn: () => fetchPost(id) });
useQuery({
  queryKey: ["posts", { page, status }],
  queryFn: () => fetchPosts({ page, status }),
});

Dado que las claves son arrays, puedes invalidar una familia completa mediante un prefijo. Invalidar ["posts"] también invalida ["posts", { page: 1 }], lo que hace que las actualizaciones de la caché sean predecibles.

Frescura y refetching

Hay dos opciones que controlan cuándo se vuelven a solicitar los datos.

// freshness.ts
useQuery({
  queryKey: ["posts"],
  queryFn: fetchPosts,
  staleTime: 60_000, // fresh for one minute
  gcTime: 5 * 60_000, // keep unused data for five minutes
});

staleTime define cuánto tiempo se consideran “frescos” los datos; mientras estén frescos, no se realizará ningún refetch en segundo plano. gcTime define cuánto tiempo permanece una entrada sin usar en la memoria antes de ser eliminada por el recolector de basura. Por defecto, las queries realizan un refetch al montarse, al recuperar el foco de la ventana y al reconectarse, lo que mantiene la UI actualizada sin necesidad de trabajo manual.

Mutations

useMutation se encarga de las escrituras: crear, actualizar y eliminar.

// AddPost.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";

export function AddPost() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (title: string) =>
      fetch("/api/posts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title }),
      }).then((r) => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["posts"] });
    },
  });

  return (
    <button
      onClick={() => mutation.mutate("New post")}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? "Saving…" : "Add post"}
    </button>
  );
}

Después de una mutación exitosa, invalidar las queries afectadas indica a cada consumidor que debe volver a realizar la petición (refetch). Esto mantiene al servidor como la fuente de verdad y evita tener que manipular la caché manualmente.

Actualizaciones optimistas

Para aquellas acciones que deben sentirse instantáneas, actualiza la caché antes de que el servidor responda y revierte los cambios en caso de error.

// optimistic.ts
useMutation({
  mutationFn: toggleLike,
  onMutate: async (postId) => {
    await queryClient.cancelQueries({ queryKey: ["post", postId] });
    const previous = queryClient.getQueryData(["post", postId]);

    queryClient.setQueryData(["post", postId], (old) => ({
      ...old,
      likes: old.likes + 1,
    }));

    return { previous };
  },
  onError: (_err, postId, context) => {
    queryClient.setQueryData(["post", postId], context.previous);
  },
  onSettled: (_data, _err, postId) => {
    queryClient.invalidateQueries({ queryKey: ["post", postId] });
  },
});

onMutate aplica el cambio optimista y guarda una instantánea del valor anterior, onError revierte los cambios y onSettled vuelve a solicitar los datos para sincronizarlos con el servidor.

Paginación y consultas infinitas

TanStack Query ofrece soporte nativo para datos paginados e infinitos. useQuery con la opción keepPreviousData hace que las transiciones entre páginas sean fluidas, y useInfiniteQuery gestiona listas basadas en cursores mediante la función fetchNextPage. Debido a que cada página forma parte de la caché, la navegación hacia adelante y hacia atrás es instantánea.

Estado del servidor frente a estado del cliente

Esta es la distinción que hace que TanStack Query tenga sentido:

  • Estado del servidor: pertenece al servidor, es compartido, asíncrono y puede quedar desactualizado (stale). Usa TanStack Query.
  • Estado del cliente: pertenece al navegador, es síncrono y local. Usa useState, Context, Zustand o Redux.

Mezclar ambos en un mismo store es el origen de mucha complejidad. Separarlos —TanStack Query para los datos y un store pequeño para el estado de la UI— mantiene cada parte simple.

Mejores prácticas

  • Incluye cada variable en la query key.
  • Define staleTime deliberadamente; el valor predeterminado de cero provoca refetches agresivos.
  • Utiliza mutations junto con invalidación en lugar de actualizar manualmente la caché en todas partes.
  • Mantén las funciones de query pequeñas y ubicadas junto a la query.
  • Añade optimistic updates solo donde la respuesta instantánea sea fundamental.
  • Utiliza las devtools para inspeccionar el estado de la caché durante el desarrollo.
  • Mantén el estado del servidor en TanStack Query y el estado del cliente en otro lugar.

Errores comunes

  • Usar la misma clave para diferentes inputs, lo que provoca la entrega de datos obsoletos o incorrectos.
  • Dejar staleTime en cero, provocando refetches constantes.
  • Duplicar datos del servidor en un store global.
  • Olvidar invalidar después de una mutación, mostrando datos desactualizados.
  • Implementar optimistic updates sin un camino de rollback.
  • Tratar las funciones de query como componentes y llamar a hooks dentro de ellas.

Próximos pasos

TanStack Query es la forma estándar de manejar datos del servidor en aplicaciones modernas. Combínalo con la Fetch API para la capa de peticiones, un store de cliente como Zustand para el estado de la UI, y Redux Toolkit si necesitas un estado global estricto. Para Vue, Pinia se encarga del estado del cliente mientras que TanStack Query cubre la parte del servidor.

Obtención de datos

useQuery almacena en caché, deduplica y expone el estado. La versión con useEffect vuelve a obtener los datos en cada montaje y requiere banderas manuales.

Preferido
const { data, isLoading, error } = useQuery({
  queryKey: ["posts"],
  queryFn: fetchPosts,
});
Evitar
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
  fetch("/api/posts")
    .then((r) => r.json())
    .then(setData)
    .finally(() => setLoading(false));
}, []);

Mantener datos actualizados

Invalida la query por clave después de una mutation para que todos los consumidores vuelvan a obtener los datos. Sin cirugías manuales del caché.

Preferido
const queryClient = useQueryClient();

const mutation = useMutation({
  mutationFn: addPost,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ["posts"],
    });
  },
});
Evitar
// refetching by hand in
// every component that
// happens to show posts

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender TanStack Query?

Nuestro tutorial interactivo te guia a traves de TanStack Query paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.