10. Consumo de APIs
🌐 10.1 Introducción al consumo de APIs
Section titled “🌐 10.1 Introducción al consumo de APIs”¿Qué es una API?
Section titled “¿Qué es una API?”Una API (Application Programming Interface) es un conjunto de endpoints que permiten a tu aplicación comunicarse con un servidor para obtener o enviar datos.
| Método HTTP | Propósito | Ejemplo |
|---|---|---|
GET | Obtener datos | Listar usuarios |
POST | Crear datos | Registrar usuario |
PUT | Actualizar todo | Editar perfil completo |
PATCH | Actualizar parcial | Cambiar solo email |
DELETE | Eliminar datos | Borrar usuario |
Flujo típico en React
Section titled “Flujo típico en React”// 1. Componente se monta// 2. useEffect hace petición a la API// 3. Mientras carga, mostrar "Cargando..."// 4. Si hay error, mostrar mensaje de error// 5. Si hay datos, renderizar la lista
function ListaUsuarios() {const [usuarios, setUsuarios] = useState([]);const [cargando, setCargando] = useState(true);const [error, setError] = useState(null);
useEffect(() => { // Petición a la API}, []);
if (cargando) return <p>Cargando...</p>;if (error) return <p>Error: {error}</p>;return <ul>{/* renderizar usuarios */}</ul>;}📡 10.2 Uso de fetch
Section titled “📡 10.2 Uso de fetch”GET - Obtener datos
Section titled “GET - Obtener datos”import { useState, useEffect } from 'react';
function ListaUsuarios() {const [usuarios, setUsuarios] = useState([]);const [cargando, setCargando] = useState(true);const [error, setError] = useState(null);
useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then(response => { if (!response.ok) { throw new Error('Error en la petición'); } return response.json(); }) .then(data => { setUsuarios(data); setCargando(false); }) .catch(err => { setError(err.message); setCargando(false); });}, []);
if (cargando) return <p>Cargando usuarios...</p>;if (error) return <p>Error: {error}</p>;
return ( <ul> {usuarios.map(usuario => ( <li key={usuario.id}>{usuario.name}</li> ))} </ul>);}GET con async/await
Section titled “GET con async/await”import { useState, useEffect } from 'react';
function ListaUsuarios() {const [usuarios, setUsuarios] = useState([]);const [cargando, setCargando] = useState(true);const [error, setError] = useState(null);
useEffect(() => { const fetchUsuarios = async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) { throw new Error('Error en la petición'); }
const data = await response.json(); setUsuarios(data); } catch (err) { setError(err.message); } finally { setCargando(false); } };
fetchUsuarios();}, []);
// ... resto del componente}POST - Enviar datos
Section titled “POST - Enviar datos”function CrearUsuario() {const [nombre, setNombre] = useState('');const [enviando, setEnviando] = useState(false);
const handleSubmit = async (e) => { e.preventDefault(); setEnviando(true);
try { const response = await fetch('https://jsonplaceholder.typicode.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: nombre, email: 'test@example.com' }), });
if (!response.ok) { throw new Error('Error al crear usuario'); }
const nuevoUsuario = await response.json(); console.log('Usuario creado:', nuevoUsuario); setNombre(''); } catch (err) { console.error(err); } finally { setEnviando(false); }};
return ( <form onSubmit={handleSubmit}> <input value={nombre} onChange={e => setNombre(e.target.value)} placeholder="Nombre" /> <button type="submit" disabled={enviando}> {enviando ? 'Creando...' : 'Crear Usuario'} </button> </form>);}📦 10.3 Uso de Axios
Section titled “📦 10.3 Uso de Axios”Instalación
Section titled “Instalación”npm install axiosGET con Axios
Section titled “GET con Axios”import { useState, useEffect } from 'react';import axios from 'axios';
function ListaUsuarios() {const [usuarios, setUsuarios] = useState([]);const [cargando, setCargando] = useState(true);const [error, setError] = useState(null);
useEffect(() => { axios.get('https://jsonplaceholder.typicode.com/users') .then(response => { setUsuarios(response.data); // Axios ya parsea JSON setCargando(false); }) .catch(err => { setError(err.message); setCargando(false); });}, []);
// ... resto del componente}POST con Axios
Section titled “POST con Axios”import axios from 'axios';
const crearUsuario = async (datos) => {try { const response = await axios.post( 'https://jsonplaceholder.typicode.com/users', datos // Axios convierte a JSON automáticamente );
console.log('Usuario creado:', response.data); return response.data;} catch (err) { console.error('Error:', err.response?.data || err.message); throw err;}};
// UsocrearUsuario({ name: 'Juan', email: 'juan@mail.com' });Comparación fetch vs Axios
Section titled “Comparación fetch vs Axios”| Característica | fetch | Axios |
|---|---|---|
| Instalación | Nativo | npm install |
| Parseo JSON | Manual (.json()) | Automático |
| Manejo de errores | Manual | Automático |
| Interceptores | No | Sí |
| Cancelación | AbortController | CancelToken |
| Timeout | Manual | Configurable |
⏳ 10.4 Estados de carga
Section titled “⏳ 10.4 Estados de carga”Patrón básico de estados
Section titled “Patrón básico de estados”import { useState, useEffect } from 'react';
function Datos() {// Estados para manejar la peticiónconst [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);
useEffect(() => { const fetchData = async () => { try { setLoading(true); setError(null);
const response = await fetch('/api/data'); const result = await response.json();
setData(result); } catch (err) { setError(err.message); } finally { setLoading(false); } };
fetchData();}, []);
// Renderizado condicionalif (loading) { return <LoadingSpinner />;}
if (error) { return <ErrorMessage message={error} />;}
if (!data) { return <EmptyState />;}
return <DataDisplay data={data} />;}Componente de Loading
Section titled “Componente de Loading”function LoadingSpinner() {return ( <div className="loading-container"> <div className="spinner"></div> <p>Cargando...</p> </div>);}
// CSS para el spinner/*.spinner {width: 40px;height: 40px;border: 4px solid #f3f3f3;border-top: 4px solid #3498db;border-radius: 50%;animation: spin 1s linear infinite;}
@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}*/Skeleton Loading
Section titled “Skeleton Loading”function SkeletonCard() {return ( <div className="skeleton-card"> <div className="skeleton-image"></div> <div className="skeleton-title"></div> <div className="skeleton-text"></div> <div className="skeleton-text short"></div> </div>);}
function ListaProductos() {const [productos, setProductos] = useState([]);const [loading, setLoading] = useState(true);
// ... fetch
if (loading) { return ( <div className="grid"> {[1, 2, 3, 4].map(i => <SkeletonCard key={i} />)} </div> );}
return ( <div className="grid"> {productos.map(p => <ProductCard key={p.id} producto={p} />)} </div>);}❌ 10.5 Manejo de errores
Section titled “❌ 10.5 Manejo de errores”Tipos de errores
Section titled “Tipos de errores”| Tipo | Código | Ejemplo |
|---|---|---|
| Cliente | 4xx | 404 Not Found, 401 Unauthorized |
| Servidor | 5xx | 500 Internal Server Error |
| Red | - | Sin conexión, timeout |
Manejo completo de errores
Section titled “Manejo completo de errores”import { useState, useEffect } from 'react';
function ListaConErrores() {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);
const fetchData = async () => { try { setLoading(true); setError(null);
const response = await fetch('/api/data');
// Verificar código de estado if (response.status === 404) { throw new Error('Recurso no encontrado'); }
if (response.status === 401) { throw new Error('No autorizado. Por favor, inicia sesión'); }
if (!response.ok) { throw new Error(`Error del servidor: ${response.status}`); }
const result = await response.json(); setData(result);
} catch (err) { // Diferenciar tipos de error if (err.name === 'TypeError') { setError('Error de conexión. Verifica tu internet.'); } else { setError(err.message); } } finally { setLoading(false); }};
useEffect(() => { fetchData();}, []);
if (error) { return ( <div className="error-container"> <p>❌ {error}</p> <button onClick={fetchData}>Reintentar</button> </div> );}
// ... resto}Componente de Error reutilizable
Section titled “Componente de Error reutilizable”function ErrorMessage({ message, onRetry }) {return ( <div className="error-box"> <span className="error-icon">⚠️</span> <p className="error-text">{message}</p> {onRetry && ( <button onClick={onRetry} className="retry-btn"> Reintentar </button> )} </div>);}
// Uso<ErrorMessagemessage="No se pudieron cargar los datos"onRetry={fetchData}/>🔄 10.6 CRUD completo
Section titled “🔄 10.6 CRUD completo”Hook personalizado para CRUD
Section titled “Hook personalizado para CRUD”import { useState } from 'react';import axios from 'axios';
const API_URL = 'https://jsonplaceholder.typicode.com/posts';
function usePosts() {const [posts, setPosts] = useState([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);
// GET - Obtener todosconst fetchPosts = async () => { setLoading(true); try { const { data } = await axios.get(API_URL); setPosts(data); } catch (err) { setError(err.message); } finally { setLoading(false); }};
// POST - Crearconst createPost = async (newPost) => { try { const { data } = await axios.post(API_URL, newPost); setPosts([...posts, data]); return data; } catch (err) { setError(err.message); throw err; }};
// PUT - Actualizarconst updatePost = async (id, updatedPost) => { try { const { data } = await axios.put(`${API_URL}/${id}`, updatedPost); setPosts(posts.map(p => p.id === id ? data : p)); return data; } catch (err) { setError(err.message); throw err; }};
// DELETE - Eliminarconst deletePost = async (id) => { try { await axios.delete(`${API_URL}/${id}`); setPosts(posts.filter(p => p.id !== id)); } catch (err) { setError(err.message); throw err; }};
return { posts, loading, error, fetchPosts, createPost, updatePost, deletePost};}Componente que usa el hook
Section titled “Componente que usa el hook”function GestorPosts() {const { posts, loading, error, fetchPosts, createPost, deletePost} = usePosts();
const [titulo, setTitulo] = useState('');
useEffect(() => { fetchPosts();}, []);
const handleCreate = async (e) => { e.preventDefault(); await createPost({ title: titulo, body: 'Contenido...' }); setTitulo('');};
const handleDelete = async (id) => { if (confirm('¿Eliminar este post?')) { await deletePost(id); }};
if (loading) return <p>Cargando...</p>;if (error) return <p>Error: {error}</p>;
return ( <div> <form onSubmit={handleCreate}> <input value={titulo} onChange={e => setTitulo(e.target.value)} placeholder="Título del post" /> <button type="submit">Crear Post</button> </form>
<ul> {posts.map(post => ( <li key={post.id}> {post.title} <button onClick={() => handleDelete(post.id)}>🗑️</button> </li> ))} </ul> </div>);}⚙️ 10.7 Configuración de Axios
Section titled “⚙️ 10.7 Configuración de Axios”Instancia de Axios
Section titled “Instancia de Axios”// services/api.jsimport axios from 'axios';
const api = axios.create({baseURL: 'https://api.example.com',timeout: 10000,headers: { 'Content-Type': 'application/json'}});
// Interceptor para agregar tokenapi.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config;},error => Promise.reject(error));
// Interceptor para manejar erroresapi.interceptors.response.use(response => response,error => { if (error.response?.status === 401) { // Redirigir a login window.location.href = '/login'; } return Promise.reject(error);});
export default api;Uso de la instancia
Section titled “Uso de la instancia”// En cualquier componenteimport api from './services/api';
// GETconst { data } = await api.get('/users');
// POSTconst { data } = await api.post('/users', { name: 'Juan' });
// PUTconst { data } = await api.put('/users/1', { name: 'Juan Actualizado' });
// DELETEawait api.delete('/users/1');📝 Resumen
Section titled “📝 Resumen”
🐝