Carga datos remotos con fetch dentro de un efecto, modelando de forma limpia los estados de carga y de error.
Abrir esta lección en KodokonUna petición de red lleva tiempo y puede fallar: tu interfaz debe reflejar esas realidades. La convención profesional es rastrear tres porciones de estado distintas: los datos recibidos, un booleano de carga y un posible error. La petición va dentro de un useEffect, porque contactar con un servidor es un efecto secundario.
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.example.com/users")
.then((res) => {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json();
})
.then((data) => setUsers(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);En el renderizado, gestiona los casos en orden: primero la carga, luego el error y, por último, los datos. Estos retornos tempranos mantienen el código legible y garantizan que el usuario siempre vea algo coherente: un indicador de espera, un mensaje de fallo o la lista esperada.
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);Una última trampa: si una dependencia cambia (un userId, por ejemplo), sale una nueva petición mientras la antigua puede seguir en curso. Si la antigua responde en último lugar, sobrescribiría los datos recientes. La contramedida clásica: un indicador ignore puesto a true por la función de limpieza, para descartar las respuestas obsoletas.
useEffect(() => {
let ignore = false;
setLoading(true);
fetch("/api/users/" + userId)
.then((res) => res.json())
.then((data) => {
if (!ignore) setUser(data);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [userId]);catch nunca se dispara. ¿Por qué?ignore, puesto a true en la limpieza del efecto?