ReactTanStack QueryTypeScriptData Fetching

TanStack Query: Guía práctica para empezar

3 min read
Vistas

Aprende a usar TanStack Query (antes React Query) para manejar datos del servidor en React. Fetching, caching y mutations en minutos.

Compartir

Si todavía estás usando useEffect + useState para hacer fetch de datos, este post es para ti. TanStack Query simplifica todo el manejo de datos del servidor: caching, refetching, loading states, errores... todo automático.

Instalación

pnpm add @tanstack/react-query

Para las devtools (opcional pero recomendado):

pnpm add @tanstack/react-query-devtools

Configuración inicial

Crea el cliente y envuelve tu app con el provider:

// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutos
      retry: 1,
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Tu primera query

El hook useQuery necesita dos cosas: una queryKey (identificador único) y una queryFn (función que obtiene los datos):

import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) {
  const { data, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then((res) => res.json()),
  });

  if (isPending) return <p>Cargando...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <h1>{data.name}</h1>;
}

La queryKey es importante: TanStack Query la usa para cachear y refrescar datos. Si userId cambia, automáticamente hace un nuevo fetch.

Mutations (crear, actualizar, eliminar)

Para modificar datos usa useMutation:

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreatePost() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newPost: { title: string }) =>
      fetch('/api/posts', {
        method: 'POST',
        body: JSON.stringify(newPost),
      }).then((res) => res.json()),
    onSuccess: () => {
      // Invalida el cache para refrescar la lista
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });

  const handleSubmit = () => {
    mutation.mutate({ title: 'Nuevo post' });
  };

  return (
    <button onClick={handleSubmit} disabled={mutation.isPending}>
      {mutation.isPending ? 'Creando...' : 'Crear post'}
    </button>
  );
}

Estados útiles

TanStack Query te da varios estados para manejar la UI:

const {
  data,           // Los datos
  isPending,      // Primera carga (sin datos en cache)
  isFetching,     // Cualquier fetch (incluye refetch en background)
  isError,        // Hubo error
  error,          // El error
  isSuccess,      // Fetch exitoso
  refetch,        // Función para refetch manual
} = useQuery({ ... });

isPending es true solo cuando no hay datos en cache. isFetching es true durante cualquier fetch, incluso refetches en background.

Ejemplo con Axios y TypeScript

Un patrón común es crear hooks personalizados:

// hooks/use-posts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

interface Post {
  id: number;
  title: string;
  body: string;
}

export function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: () => axios.get<Post[]>('/api/posts').then((res) => res.data),
  });
}

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

  return useMutation({
    mutationFn: (data: Omit<Post, 'id'>) => axios.post<Post>('/api/posts', data).then((res) => res.data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });
}

Uso en componentes:

function PostList() {
  const { data: posts, isPending } = usePosts();
  const createPost = useCreatePost();

  if (isPending) return <Skeleton />;

  return (
    <div>
      {posts?.map((post) => (
        <PostCard key={post.id} post={post} />
      ))}
      <button onClick={() => createPost.mutate({ title: 'Test', body: '...' })}>Agregar</button>
    </div>
  );
}

Conclusión

TanStack Query elimina mucho boilerplate del manejo de datos. Lo que antes requería múltiples useState, useEffect y lógica de caching manual, ahora son unas pocas líneas.

Para profundizar más, revisa la documentación oficial que está muy completa.


¿Te gustó este artículo?

Artículos relacionados