45. useEffect - ¿Qué es y para qué se usa?
Tutorial: useEffect en React - ¿Qué es y para qué se usa?
🎯 Introducción
Seguramente alguna vez hayas tenido que realizar operaciones como:
Recuperación de datos desde una API
Suscripciones a servicios externos
Modificación manual del DOM desde componentes
Estas operaciones se llaman efectos secundarios porque pueden afectar a otros componentes y no pueden realizarse durante el renderizado. Se deben ejecutar cuando el renderizado ya ha finalizado.
🔍 ¿Qué es useEffect?
useEffect es un Hook de React que nos permite realizar efectos secundarios desde componentes funcionales. Reemplaza los métodos del ciclo de vida de los componentes de clase:
componentDidMount
componentDidUpdate
componentWillUnmount
Todo esto se unifica en un solo Hook: useEffect.
📝 Sintaxis Básica
javascript
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// useEffect se ejecuta después de cada renderizado
useEffect(() => {
// Efecto secundario aquí
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
🔄 ¿Cuándo se ejecuta useEffect?
Después del renderizado inicial (equivalente a componentDidMount)
Después de cada actualización (equivalente a componentDidUpdate)
Antes de desmontar el componente (con función de limpieza, equivalente a componentWillUnmount)
🎨 Ejercicio Práctico
Vamos a analizar el código de la imagen adjunta:
javascript
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar a componentDidMount y componentDidUpdate
useEffect(() => {
// Actualiza el título del documento usando la API del navegador
document.title = 'You clicked ${count} times'; // NOTA: Hay un error aquí
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
⚠️ Corrección necesaria:
En el código hay un error común: se usan comillas simples en lugar de backticks para la template string:
Incorrecto: 'You clicked ${count} times'
Correcto: `You clicked ${count} times`
📊 ¿Qué hace este código?
Estado: Crea un estado count inicializado en 0
Efecto: Cada vez que count cambia (después del renderizado), actualiza el título de la página
Interfaz: Muestra el contador y un botón para incrementarlo
🎯 Resumen
💡 Puntos Clave
useEffect se ejecuta después del renderizado
Puede depender de variables específicas (con el array de dependencias)
Puede devolver una función de limpieza
Reemplaza múltiples métodos del ciclo de vida en uno solo
🚀 Próximos Pasos
En el siguiente tutorial veremos:
Array de dependencias ([], [variable], sin array)
Funciones de limpieza
useEffect para llamadas a APIs
Buenas prácticas y errores comunes
❓ ¿Dudas?
Si tienes preguntas sobre useEffect, recuerda que:
Se ejecuta después del renderizado
Puede controlar efectos secundarios
Es más simple que los métodos de ciclo de vida de clases
¡Practica con el ejemplo y nos vemos en el siguiente tutorial!
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 APIs1. 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
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
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
// 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