Patrones de Obtención de Datos
Obtención de datos moderna en React — desde useEffect hasta TanStack Query, SWR, Suspense y Componentes de Servidor.
Patrones de Obtención de Datos
La obtención de datos es una de las tareas más comunes en aplicaciones React, y el ecosistema ha evolucionado significativamente. Esta guía cubre desde la obtención manual con useEffect hasta librerías listas para producción como TanStack Query y SWR.
⚠️ La Evolución
useEffect + fetch está bien para aprender, pero las apps de producción deberían usar una librería de datos. Estas manejan caché, revalidación, reintentos y condiciones de carrera — problemas difíciles de resolver correctamente a mano.
1. Patrón Básico: useEffect + fetch
import { useState, useEffect } from "react";
function PerfilUsuario({ userId }) {
const [usuario, setUsuario] = useState(null);
const [error, setError] = useState(null);
const [cargando, setCargando] = useState(true);
useEffect(() => {
const controller = new AbortController();
async function obtenerUsuario() {
setCargando(true);
try {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setUsuario(await res.json());
} catch (err) {
if (err.name !== "AbortError") setError(err.message);
} finally {
setCargando(false);
}
}
obtenerUsuario();
return () => controller.abort();
}, [userId]);
if (cargando) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return <div>{usuario.nombre}</div>;
}
2. TanStack Query (React Query)
La librería más popular para obtención de datos en React. Maneja caché, revalidación en segundo plano, paginación y actualizaciones optimistas.
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function Posts() {
const { data: posts, isLoading, error } = useQuery({
queryKey: ["posts"],
queryFn: () => fetch("/api/posts").then((r) => r.json()),
staleTime: 5 * 60 * 1000, // 5 min antes de ser "obsoleto"
retry: 3, // Reintentar solicitudes fallidas
});
if (isLoading) return <p>Cargando...</p>;
if (error) return <p>Error: {error.message}</p>;
return posts.map((post) => <h3 key={post.id}>{post.titulo}</h3>);
}
Mutaciones con TanStack Query
function CrearPost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (nuevoPost) =>
fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(nuevoPost),
}).then((r) => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
},
});
return (
<button onClick={() => mutation.mutate({ titulo: "Nuevo" })}>
{mutation.isPending ? "Creando..." : "Crear Post"}
</button>
);
}
3. SWR (Stale-While-Revalidate)
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
function PerfilUsuario({ userId }) {
const { data, error, isLoading } = useSWR(
`/api/users/${userId}`,
fetcher,
{
revalidateOnFocus: true,
revalidateOnReconnect: true,
}
);
if (isLoading) return <p>Cargando...</p>;
if (error) return <p>Error al cargar usuario</p>;
return <h2>{data.nombre}</h2>;
}
4. Suspense para Datos
import { Suspense } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
function DetallesUsuario({ userId }) {
const { data: usuario } = useSuspenseQuery({
queryKey: ["user", userId],
queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
});
return <h2>{usuario.nombre}</h2>;
}
function PaginaUsuario({ userId }) {
return (
<Suspense fallback={<p>Cargando...</p>}>
<DetallesUsuario userId={userId} />
</Suspense>
);
}
✅ ¿Qué Librería Usar?
| Escenario | Recomendación |
|---|---|
| Aprendizaje / app simple | Hook personalizado useFetch |
| SPA del lado del cliente | TanStack Query |
| Next.js / Stack Vercel | SWR |
| Next.js App Router | Server Components + TanStack Query para partes del cliente |