TanStack Query: Guía práctica para empezar
Aprende a usar TanStack Query (antes React Query) para manejar datos del servidor en React. Fetching, caching y mutations en minutos.
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.