29. useEffect
Tutorial: 29. useEffect en React
Introducción a useEffect
useEffect es uno de los Hooks más importantes en React que nos permite ejecutar efectos secundarios en nuestros componentes funcionales. Los efectos secundarios son operaciones que pueden incluir peticiones a APIs, suscripciones, manipulación del DOM, etc.
1. Sintaxis Básica
La sintaxis básica de useEffect es:
javascript
useEffect(() => {
// Código a ejecutar
}, []) // Array de dependencias
Ejemplo visual de la sintaxis:
text
useEffect(()=>{},[])
2. Ejemplo Práctico: Sistema de Likes
Vamos a construir un componente de likes para entender cómo funciona useEffect:
javascript
import { useState, useEffect } from "react";
export function Prueba() {
const [likes, setLikes] = useState(0);
const darLike = () => {
setLikes(likes + 1);
};
// useEffect ejecutando darLike una sola vez
useEffect(darLike, []);
return (
<div>
<h1>Likes {likes}</h1>
<button onClick={darLike}>Dar like</button>
</div>
);
}
¿Qué hace este código?
Creamos un estado likes que inicia en 0
La función darLike incrementa el contador
El botón ejecuta darLike cuando hacemos clic
useEffect ejecuta darLike una sola vez cuando el componente se monta
3. Herramienta de Desarrollo: React Developer Tools
Para depurar y visualizar mejor nuestros componentes y estados:
Instala React Developer Tools desde la Chrome Web Store
Abre las herramientas de desarrollo (F12)
Ve a la pestaña "Components"
Podrás ver:
Todos los componentes renderizados
Sus estados y props
Cómo se actualizan en tiempo real
Beneficios:
Visualizas el estado actual (likes)
Ves cómo se modifican los valores
Identificas qué componentes se actualizan
4. Casos de Uso Real de useEffect
A. Actualización de Carrito de Compras
javascript
// Cuando añades un producto al carrito
useEffect(() => {
actualizarContadorCarrito();
}, [productosEnCarrito]); // Se ejecuta cuando productosEnCarrito cambia
B. Peticiones a APIs
javascript
useEffect(() => {
fetch('/api/datos')
.then(res => res.json())
.then(data => setDatos(data));
}, []); // Se ejecuta solo al montar el componente
C. Suscripciones y Eventos
javascript
useEffect(() => {
const subscription = api.subscribe();
// Función de limpieza
return () => {
subscription.unsubscribe();
};
}, []);
5. El Array de Dependencias []
Ejecutar una sola vez:
javascript
useEffect(() => {
console.log("Se ejecuta solo al montar el componente");
}, []); // Array vacío
Ejecutar cuando una variable cambia:
javascript
useEffect(() => {
console.log("Se ejecuta cuando 'likes' cambia");
}, [likes]); // Observa cambios en 'likes'
⚠️ PELIGRO: Bucle infinito
javascript
useEffect(() => {
setLikes(likes + 1); // ❌ ¡CUIDADO!
}); // Sin array de dependencias = se ejecuta en cada render
¿Por qué ocurre el bucle infinito?
useEffect se ejecuta
Actualiza el estado (setLikes)
El componente se re-renderiza
useEffect se ejecuta de nuevo...
¡Bucle infinito!
6. Cómo Funciona Internamente
useEffect actúa como un observador:
Observa las variables en el array de dependencias
Compara sus valores actuales con los anteriores
Ejecuta la función si detecta cambios
Opcionalmente ejecuta una función de limpieza
7. Ejemplo Completo: useEffect en Acción
javascript
import { useState, useEffect } from "react";
function ContadorConEfecto() {
const [contador, setContador] = useState(0);
const [actualizaciones, setActualizaciones] = useState(0);
// Efecto que se ejecuta al montar
useEffect(() => {
console.log("Componente montado");
setContador(1); // Inicia en 1
}, []);
// Efecto que se ejecuta cuando contador cambia
useEffect(() => {
console.log(`Contador cambió a: ${contador}`);
setActualizaciones(prev => prev + 1);
}, [contador]); // Dependencia: contador
return (
<div>
<h2>Contador: {contador}</h2>
<h3>Actualizaciones: {actualizaciones}</h3>
<button onClick={() => setContador(contador + 1)}>
Incrementar
</button>
</div>
);
}
8. Buenas Prácticas
Siempre incluye el array de dependencias
Usa funciones de limpieza para suscripciones
Separa efectos por responsabilidad
Evita efectos innecesarios en cada render
Mueve lógica compleja a custom hooks
9. Resumen
10. Conclusión
useEffect es una herramienta poderosa para:
Sincronizar componentes con sistemas externos
Ejecutar código en momentos específicos del ciclo de vida
Manejar efectos secundarios de manera controlada
Optimizar rendimiento evitando ejecuciones innecesarias
Recuerda: ¡Siempre especifica el array de dependencias! Esto previene bucles infinitos y hace tu código más predecible.
Ejercicio propuesto: Crea un componente que:
Cargue datos de una API al montarse
Actualice un contador cada 5 segundos
Limpie el intervalo al desmontarse
Muestre un mensaje cuando los datos se carguen
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
jsx
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
jsx
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
jsx
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)
jsx
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
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
// 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