13.1-useEffect para Llamadas a APIs: Guía Práctica
useEffect para Llamadas a APIs: Guía Práctica
useEffect es el hook de React que te permite sincronizar tu componente con sistemas externos, como APIs, bases de datos, o cualquier operación que ocurre "fuera" de React.
La idea principal
Cuando necesitas traer datos de una API cuando el componente se monta, o cuando cierta variable cambia, usas useEffect.
Estructura básica:
jsx
useEffect(() => {
// Código que se ejecuta (efecto)
return () => {
// Código de limpieza (opcional)
};
}, [dependencias]); // Array de dependencias
Ejemplo más simple: Traer datos al montar
import { useState, useEffect } from 'react';
function Usuario() {
const [usuario, setUsuario] = useState(null);
const [cargando, setCargando] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Esta función se ejecuta cuando el componente se monta
const fetchUsuario = async () => {
try {
setCargando(true);
const response = await fetch('https://api.ejemplo.com/usuario/1');
const data = await response.json();
setUsuario(data);
} catch (err) {
setError('Error al cargar usuario');
} finally {
setCargando(false);
}
};
fetchUsuario();
}, []); // Array vacío = se ejecuta solo al montar
if (cargando) return <p>Cargando...</p>;
if (error) return <p>{error}</p>;
return (
<div>
<h1>{usuario.nombre}</h1>
<p>Email: {usuario.email}</p>
</div>
);
}
Patrones comunes para APIs-1. Búsqueda con parámetros
function BuscadorProductos() {
const [productos, setProductos] = useState([]);
const [busqueda, setBusqueda] = useState('');
const [cargando, setCargando] = useState(false);
// Se ejecuta cuando cambia 'busqueda'
useEffect(() => {
// Evitar búsqueda si está vacío
if (!busqueda.trim()) {
setProductos([]);
return;
}
const buscarProductos = async () => {
setCargando(true);
try {
const response = await fetch(
`https://api.ejemplo.com/productos?q=${busqueda}`
);
const data = await response.json();
setProductos(data);
} catch (error) {
console.error('Error:', error);
} finally {
setCargando(false);
}
};
// Debounce: espera 300ms antes de hacer la petición
const timer = setTimeout(buscarProductos, 300);
// Cleanup: cancela la petición si el usuario sigue escribiendo
return () => clearTimeout(timer);
}, [busqueda]); // Dependencia: se ejecuta cuando 'busqueda' cambia
return (
<div>
<input
type="text"
value={busqueda}
onChange={(e) => setBusqueda(e.target.value)}
placeholder="Buscar productos..."
/>
{cargando && <p>Buscando...</p>}
<ul>
{productos.map((producto) => (
<li key={producto.id}>{producto.nombre}</li> ))}
</ul> </div> );}
2. Datos que dependen de otros datos
function PerfilUsuario({ userId }) {
const [usuario, setUsuario] = useState(null);
const [posts, setPosts] = useState([]);
// Traer usuario cuando cambie userId
useEffect(() => {
const fetchUsuario = async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
const data = await response.json();
setUsuario(data);
};
fetchUsuario();
}, [userId]); // Se ejecuta cuando userId cambia
// Traer posts del usuario cuando usuario esté disponible
useEffect(() => {
if (!usuario) return; // No ejecutar si no hay usuario
const fetchPosts = async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?userId=${userId}`
);
const data = await response.json();
setPosts(data);
};
fetchPosts();
}, [usuario, userId]); // Depende de usuario y userId
// Renderizar...
}
3. POST a una API (crear recursos)
function FormularioNuevoPost() {
const [titulo, setTitulo] = useState('');
const [cuerpo, setCuerpo] = useState('');
const [enviando, setEnviando] = useState(false);
const [mensaje, setMensaje] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setEnviando(true);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: titulo,
body: cuerpo,
userId: 1,
}),
});
const data = await response.json();
setMensaje('¡Post creado con éxito!');
setTitulo('');
setCuerpo('');
console.log('Respuesta:', data);
} catch (error) {
setMensaje('Error al crear post');
} finally {
setEnviando(false);
}
};
// Limpiar mensaje después de 3 segundos
useEffect(() => {
if (mensaje) {
const timer = setTimeout(() => {
setMensaje('');
}, 3000);
return () => clearTimeout(timer);
} }, [mensaje]);
return (
<form onSubmit={handleSubmit}>
<input
value={titulo}
onChange={(e) => setTitulo(e.target.value)}
placeholder="Título"
/>
<textarea
value={cuerpo}
onChange={(e) => setCuerpo(e.target.value)}
placeholder="Contenido"
/>
<button type="submit" disabled={enviando}>
{enviando ? 'Enviando...' : 'Crear Post'}
</button>
{mensaje && <p>{mensaje}</p>}
</form>
);
}
Manejo de errores robusto
jsx
function ApiConManejoErrores() {
const [datos, setDatos] = useState(null);
const [estado, setEstado] = useState('idle'); // 'idle', 'loading', 'success', 'error'
useEffect(() => {
const fetchData = async () => {
setEstado('loading');
try {
// Simular error aleatorio
if (Math.random() > 0.7) {
throw new Error('Error de conexión simulado');
}
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setDatos(data);
setEstado('success');
} catch (error) {
setEstado('error');
console.error('Error en fetch:', error);
}
};
fetchData();
}, []);
// Renderizar según estado
switch (estado) {
case 'idle':
return null;
case 'loading':
return <div>Cargando datos...</div>;
case 'error':
return <div>Error al cargar datos. Intenta de nuevo.</div>;
case 'success':
return (
<div>
<h2>{datos.title}</h2>
<p>ID: {datos.id}</p>
<p>Completado: {datos.completed ? 'Sí' : 'No'}</p>
</div>
);
default:
return null;
}
}
Buenas prácticas para APIs
1. Abortar peticiones al desmontar
jsx
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
const response = await fetch('https://api.ejemplo.com/datos', { signal });
// Procesar respuesta
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch abortado');
} else {
// Manejar otros errores
}
}
};
fetchData();
// Cleanup: aborta la petición si el componente se desmonta
return () => controller.abort();
}, []);
2. Custom Hook reutilizable
jsx
// hooks/useApi.js
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) throw new Error('Error en la petición');
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// Uso en componente
function MiComponente() {
const { data, loading, error } = useApi('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Cargando...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Reglas de oro para useEffect con APIs
Siempre maneja estados de carga y error
Usa cleanup para abortar peticiones pendientes
No olvides las dependencias en el array
Evita llamadas innecesarias con condiciones
Separa responsabilidades en múltiples useEffect si es necesario
Considera usar una librería como React Query o SWR para casos complejos
Resumen visual
text
useEffect(() => {
// 1. Iniciar carga
// 2. Hacer fetch a la API
// 3. Procesar respuesta
// 4. Actualizar estado
return () => {
// Limpiar (abortar fetch, limpiar timeouts)
};
}, [dependencias]); // ¿Cuándo se ejecuta?
¡Comienza con ejemplos simples y ve incrementando la complejidad! La práctica es clave para dominar useEffect con APIs
Comentarios
Publicar un comentario