useEffect es el hook de React que ejecuta código fuera del flujo de renderizado, como llamadas a una API, temporizadores o suscripciones.
Cada vez que React pinta un componente en pantalla, useEffect se ejecuta después. Esto lo hace ideal para operaciones que no forman parte del render visual.
¿Qué es un efecto secundario?
Un efecto secundario es cualquier operación que afecta algo fuera del componente. Ejemplos comunes:
- Consultar una API de productos de Mercado Libre
- Iniciar un temporizador con
setInterval - Guardar datos en
localStorage - Suscribirse a un WebSocket
React separa el renderizado (pintar la UI) de los efectos (todo lo demás). useEffect es el puente entre los dos.
Sintaxis de useEffect
useEffect(función, [dependencias]);
Tiene dos partes obligatorias:
| Parte | Descripción |
|---|---|
función |
El código que quieres ejecutar como efecto |
[dependencias] |
Arreglo que controla cuándo se vuelve a ejecutar el efecto |
El arreglo de dependencias es lo más importante de useEffect. Controla completamente el ciclo de vida del efecto.
Tres modos de ejecución
El arreglo de dependencias define cuándo corre tu efecto. Hay tres casos:
1. Sin arreglo — se ejecuta en cada render
useEffect(() => {
console.log("El componente se renderizó");
});
Esto corre después de cada actualización del componente. Rara vez es lo que necesitas.
2. Arreglo vacío [] — se ejecuta solo al montar
useEffect(() => {
console.log("Solo al montar el componente");
}, []);
Este es el equivalente de componentDidMount en clases. Úsalo para cargar datos iniciales.
3. Arreglo con dependencias — se ejecuta cuando cambia una variable
useEffect(() => {
console.log("El valor cambió:", busqueda);
}, [busqueda]);
El efecto corre solo cuando busqueda cambia. Perfecto para búsquedas en tiempo real.
Ejemplo 1 — Cargar productos de una API al montar
Imagina que construyes una tienda similar a Liverpool. Al cargar la página, necesitas traer los productos del servidor.
import { useState, useEffect } from "react";
function CatalogoProductos() {
const [productos, setProductos] = useState([]);
const [cargando, setCargando] = useState(true);
useEffect(() => {
fetch("https://api.liverpool.com.mx/productos")
.then((res) => res.json())
.then((data) => {
setProductos(data);
setCargando(false);
});
}, []);
if (cargando) return <p>Cargando productos...</p>;
return (
<ul>
{productos.map((p) => (
<li key={p.id}>{p.nombre} — ${p.precio.toLocaleString("es-MX")}</li>
))}
</ul>
);
}
El arreglo vacío [] garantiza que la llamada al API ocurre una sola vez. Sin él, el componente haría la llamada en bucle infinito.
Ejemplo 2 — Filtrar productos según búsqueda
Ahora el usuario puede escribir en un campo de búsqueda. Cada vez que cambia el texto, quieres consultar el API de nuevo.
function BuscadorFEMSA() {
const [termino, setTermino] = useState("");
const [resultados, setResultados] = useState([]);
useEffect(() => {
if (termino === "") {
setResultados([]);
return;
}
fetch(`https://api.femsa.com/buscar?q=${termino}`)
.then((res) => res.json())
.then((data) => setResultados(data));
}, [termino]);
return (
<div>
<input
value={termino}
onChange={(e) => setTermino(e.target.value)}
placeholder="Buscar producto FEMSA..."
/>
<ul>
{resultados.map((r) => (
<li key={r.id}>{r.nombre}</li>
))}
</ul>
</div>
);
}
Aquí [termino] le dice a React: "ejecuta este efecto cada vez que termino cambie". Si el usuario borra el campo, el efecto limpia los resultados y sale con return.
La función de limpieza (cleanup)
Algunos efectos deben detenerse cuando el componente se desmonta. Por ejemplo, un temporizador que sigue corriendo aunque el usuario ya cambió de página.
Para eso, useEffect acepta una función de retorno que React llama al limpiar el efecto.
useEffect(() => {
const intervalo = setInterval(() => {
console.log("Verificando stock de Bimbo...");
}, 5000);
return () => {
clearInterval(intervalo);
console.log("Temporizador detenido");
};
}, []);
Cuando el componente desaparece de la pantalla, React ejecuta el return. El clearInterval detiene el temporizador. Sin esto, el intervalo seguiría corriendo en memoria.
Ejemplo 3 — Temporizador con limpieza en un dashboard
Imagina un dashboard de ventas de Mercado Libre que actualiza el total cada 10 segundos.
function DashboardVentas() {
const [totalVentas, setTotalVentas] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
fetch("https://api.mercadolibre.com/ventas/total")
.then((res) => res.json())
.then((data) => setTotalVentas(data.total));
}, 10000);
return () => clearInterval(timer);
}, []);
return (
<div>
<h2>Ventas del día</h2>
<p>Total: ${totalVentas.toLocaleString("es-MX")}</p>
</div>
);
}
El arreglo vacío significa que el intervalo se crea solo una vez. La función de limpieza lo destruye cuando el componente se desmonta.
Errores comunes
Error 1 — Olvidar el arreglo de dependencias
// ❌ Incorrecto: corre en cada render
useEffect(() => {
fetch("/api/productos").then(...);
});
// ✅ Correcto: corre solo al montar
useEffect(() => {
fetch("/api/productos").then(...);
}, []);
Sin el arreglo, el efecto llama al API en cada renderizado. Esto puede generar cientos de llamadas innecesarias.
Error 2 — Bucle infinito por dependencia que cambia dentro del efecto
// ❌ Bucle infinito
const [datos, setDatos] = useState([]);
useEffect(() => {
fetch("/api/datos")
.then((res) => res.json())
.then((data) => setDatos(data)); // cambia 'datos'
}, [datos]); // pero 'datos' está en las dependencias
Cada vez que setDatos corre, datos cambia. Eso dispara el efecto de nuevo. Eso vuelve a cambiar datos. Y así en bucle infinito.
Solución: Quita datos del arreglo si no lo necesitas como disparador.
// ✅ Correcto
useEffect(() => {
fetch("/api/datos")
.then((res) => res.json())
.then((data) => setDatos(data));
}, []); // sin 'datos' en dependencias
Error 3 — No limpiar efectos con temporizadores o suscripciones
// ❌ El intervalo nunca se detiene
useEffect(() => {
setInterval(() => console.log("tick"), 1000);
}, []);
// ✅ Se limpia al desmontar
useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(id);
}, []);
Sin la función de limpieza, el intervalo sigue corriendo aunque el componente ya no exista. Esto causa fugas de memoria.
Tabla resumen: arreglo de dependencias
| Arreglo | ¿Cuándo corre el efecto? | Caso de uso típico |
|---|---|---|
| Sin arreglo | En cada render | Depuración, logging |
[] vacío |
Solo al montar | Cargar datos iniciales |
[var1, var2] |
Cuando var1 o var2 cambian |
Búsqueda reactiva, filtros |
Comparación: con y sin limpieza
| Efecto | ¿Necesita limpieza? | Ejemplo |
|---|---|---|
fetch a una API |
No siempre | Cargar catálogo de productos |
setInterval |
Sí | Dashboard de ventas |
addEventListener |
Sí | Detectar scroll o teclas |
setTimeout |
Sí si es largo | Notificación automática |
Puntos clave para recordar
useEffectse ejecuta después de que React pinta el componente, nunca durante.- El arreglo de dependencias controla cuándo se repite el efecto. Nunca lo omitas sin pensar.
- Un arreglo vacío
[]equivale a "ejecutar solo al montar". Es el caso más común para cargar datos. - La función de retorno dentro de
useEffectlimpia el efecto anterior. Úsala con temporizadores y suscripciones. - Si ves un bucle infinito, revisa si alguna variable que
seteasdentro del efecto también está en las dependencias.