← React & TypeScript

Hooks

¿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, nombreCompleto a partir de nombre y apellido), 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.

Reto rápido

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>;
}