¿Para qué sirve useEffect y cuándo se ejecuta? ¿Qué diferencia hay entre pasar `[]`, pasar dependencias o no pasar arreglo?
Ver respuesta — intenta responderla en voz alta primero
useEffect sirve para sincronizar el componente con algo externo a React (timers, suscripciones, peticiones, el DOM). Se ejecuta DESPUÉS de que React pinta el render, y el arreglo de dependencias controla cuándo se repite.
El render debe ser una función pura: recibe props y estado, devuelve JSX. Todo lo que toca el mundo exterior (un setInterval, un addEventListener, un fetch) es un efecto secundario, y useEffect es el lugar para eso.
Los puntos clave:
- Se ejecuta después del render, nunca durante. Primero React actualiza la pantalla, luego corre tus efectos.
[](arreglo vacío): el efecto corre solo una vez, al montar el componente.[deps]: corre al montar y cada vez que alguna dependencia cambie entre renders.- Sin arreglo: corre después de CADA render. Casi nunca es lo que quieres.
- Cleanup: si el efecto devuelve una función, React la ejecuta antes de repetir el efecto y al desmontar. Ahí limpias timers, suscripciones y listeners para evitar fugas de memoria.
- No es para todo. Si un valor se puede calcular a partir de props o estado (por ejemplo,
nombreCompletoa partir denombreyapellido), calcúlalo directo en el render. Usar un effect más un estado extra para eso genera renders de más y bugs de sincronización.
import { useEffect, useState } from "react";
type Props = { segundosLimite: number };
function Cronometro({ segundosLimite }: Props) {
const [segundos, setSegundos] = useState(0);
const [nombre, setNombre] = useState("Ana");
useEffect(() => {
// Corre al montar y cada vez que cambie segundosLimite
const id = setInterval(() => {
// Forma funcional: no necesitamos "segundos" como dependencia
setSegundos(prev => (prev < segundosLimite ? prev + 1 : prev));
}, 1000);
// Cleanup: React lo llama antes de re-ejecutar el efecto y al desmontar.
// Sin esto, acumularíamos intervalos vivos (fuga de memoria).
return () => clearInterval(id);
}, [segundosLimite]);
// Estado derivado: se calcula en el render, NO necesita useEffect
const saludo = `Hola, ${nombre}. Llevas ${segundos}s`;
return (
<div>
<p>{saludo}</p>
<input value={nombre} onChange={e => setNombre(e.target.value)} />
</div>
);
}
Usar useEffect para "copiar" un valor derivado a otro estado:
// MAL: estado extra + effect para algo que se calcula solo
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(precio * cantidad);
}, [precio, cantidad]);
// Provoca un render adicional y puede quedar desincronizado
// BIEN: derivar directo en el render
const total = precio * cantidad;
Otro tropiezo clásico es olvidar el cleanup de un timer o listener: cada re-render crea uno nuevo y el anterior sigue vivo. La regla es simple: si el efecto crea algo que persiste, el cleanup lo destruye.
useEffect es para sincronizar el componente con sistemas externos: timers, suscripciones, peticiones o APIs del navegador. Se ejecuta después de que React pinta, y el arreglo de dependencias controla la frecuencia: con arreglo vacío corre solo al montar, con dependencias corre cuando alguna cambia, y sin arreglo corre en cada render. Si el efecto crea algo persistente, devuelvo una función de cleanup que React ejecuta antes de repetir el efecto y al desmontar. Y algo que me gusta aclarar: no todo va en un effect; el estado derivado se calcula directo en el render, sin estado extra.
Crea un hook de componente AvisoOnline que muestre "Conectado" o "Sin conexión" escuchando los eventos online y offline de window, con su cleanup correcto.
Ver solución
import { useEffect, useState } from "react";
function AvisoOnline() {
const [conectado, setConectado] = useState<boolean>(navigator.onLine);
useEffect(() => {
const marcarOnline = () => setConectado(true);
const marcarOffline = () => setConectado(false);
window.addEventListener("online", marcarOnline);
window.addEventListener("offline", marcarOffline);
// Cleanup: quitamos exactamente los mismos listeners
return () => {
window.removeEventListener("online", marcarOnline);
window.removeEventListener("offline", marcarOffline);
};
}, []); // solo al montar: los listeners viven toda la vida del componente
return <p>{conectado ? "Conectado" : "Sin conexión"}</p>;
}