Llamadas a APIs con fetch
Una API ofrece datos a través de una dirección. fetch realiza la petición y devuelve una respuesta. La interfaz debe distinguir al menos cargando, error y datos disponibles.
Separar la petición en un servicio #
Crea src/services/pokemon.ts en el proyecto de prácticas:
1export interface Pokemon { 2 name: string; 3} 4 5interface PokemonResponse { 6 results: Pokemon[]; 7} 8 9export async function loadPokemons(signal: AbortSignal): Promise<Pokemon[]> { 10 const response = await fetch('https://pokeapi.co/api/v2/pokemon?limit=10', { 11 signal, 12 }); 13 if (!response.ok) { 14 throw new Error(`Error HTTP ${response.status}`); 15 } 16 const data: PokemonResponse = await response.json(); 17 return data.results; 18}
fetch no rechaza automáticamente una respuesta HTTP como 404 o 500. Por eso comprobamos response.ok. response.json() también es asíncrono y convierte el cuerpo JSON en datos JavaScript. La interfaz TypeScript documenta la estructura esperada, pero no valida el JSON en tiempo de ejecución.
Utilizar el servicio desde el componente #
En src/App.tsx:
1import { useEffect, useState } from 'react'; 2import { loadPokemons, type Pokemon } from './services/pokemon'; 3 4export default function Ejem_15() { 5 const [pokemons, setPokemons] = useState<Pokemon[]>([]); 6 const [loading, setLoading] = useState(true); 7 const [error, setError] = useState<string | null>(null); 8 9 useEffect(() => { 10 const controller = new AbortController(); 11 let active = true; 12 13 async function load() { 14 try { 15 const data = await loadPokemons(controller.signal); 16 if (active) setPokemons(data); 17 } catch (cause) { 18 if (active) { 19 setError(cause instanceof Error ? cause.message : 'Error desconocido'); 20 } 21 } finally { 22 if (active) setLoading(false); 23 } 24 } 25 26 void load(); 27 return () => { 28 active = false; 29 controller.abort(); 30 }; 31 }, []); 32 33 if (loading) return <p>Cargando la lista de Pokémon...</p>; 34 if (error) return <p role="alert">No se pudo cargar: {error}</p>; 35 if (pokemons.length === 0) return <p>No hay resultados.</p>; 36 37 return ( 38 <ul> 39 {pokemons.map((pokemon) => ( 40 <li key={pokemon.name}>{pokemon.name}</li> 41 ))} 42 </ul> 43 ); 44}
No hacemos async directamente la función que recibe useEffect, porque debe devolver una limpieza o nada, no una promesa. Declaramos una función asíncrona dentro y la llamamos.
La limpieza cancela la petición y marca esa ejecución como inactiva. Así una respuesta de un efecto que ya terminó no sobrescribe el estado de una ejecución más reciente. Una cancelación causada por desmontaje tampoco se muestra como error.
Prueba tú #
Comprueba la carga con la red ralentizada en las herramientas del navegador. Activa el modo sin conexión para observar el error y recarga con conexión para ver la lista. Esta práctica necesita conexión a la API pública.
Cuestionario del tema #
Abre el cuestionario en las actividades de este tema para comprobar lo aprendido. Cada respuesta incluye una explicación.