Pedir datos a una API
Obtener datos de un servidor es un efecto secundario clásico: ocurre fuera de
React, es asíncrono y no debe pasar durante el render. Por eso el patrón habitual
es lanzar la petición dentro de un useEffect y guardar el resultado en estado.
Como la red tarda y puede fallar, se modela con tres estados:
loading→ la petición está en curso (aún no hay datos).error→ la petición falló (mostramos un mensaje).data→ llegaron los datos (los pintamos).
function Usuario({ id }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let activo = true; // para ignorar respuestas obsoletas
setLoading(true);
setError(null);
fetch("/api/usuarios/" + id)
.then((res) => {
if (!res.ok) throw new Error("Respuesta " + res.status);
return res.json();
})
.then((json) => {
if (activo) { setData(json); setLoading(false); }
})
.catch((err) => {
if (activo) { setError(err.message); setLoading(false); }
});
return () => { activo = false; }; // limpieza: evita "race conditions"
}, [id]);
if (loading) return <p>Cargando…</p>;
if (error) return <p>Error: {error}</p>;
return <h2>{data.nombre}</h2>;
}
Puntos clave de este patrón:
- Mientras llegan los datos se muestra un estado de carga (
Cargando…). El usuario nunca ve una pantalla rota a la espera de la red. - La petición va en
useEffectcon[id]como dependencia: si cambia elid, se vuelve a cargar el usuario correcto. - La limpieza (
activo = false) descarta respuestas que llegan tarde, cuando el componente ya pidió otra cosa o se desmontó (evita las race conditions).
En aplicaciones reales esta lógica suele delegarse en librerías como React Query o SWR, que gestionan caché, reintentos y estados por ti. Pero todas se apoyan en este mismo patrón
loading / error / data.