react-hook-useeffect

El Hook useEffect en React

  • 5 min

Hasta ahora, nuestros componentes han sido funciones puras: reciben datos, renderizan UI y responden a eventos del usuario.

Pero las aplicaciones reales necesitan hacer cosas fuera de su mundo de componente. Necesitan pedir datos a un servidor, suscribirse a eventos del teclado, manipular el DOM directamente o poner temporizadores.

En la programación funcional, a estas operaciones que ocurren “fuera” del cálculo del resultado de la función se les llama Side Effects.

Para poder integrar este tipo de acciones dentro de la filosofía declarativa de React, tenemos el Hook useEffect.

¿Qué es useEffect?

useEffect es un Hook que nos permite ejecutar código después de que React haya renderizado el componente y actualizado el DOM.

Pensad en él como el lugar donde decimos:

React, una vez que hayas terminado de pintar la pantalla, haz esto otro

La sintaxis básica de useEffect acepta dos argumentos:

import { useEffect } from 'react';

useEffect(() => {
  // Código del efecto (peticiones API, suscripciones...)
}, [/* dependencias */]);
Copied!
  1. Una función de efecto (el código a ejecutar).
  2. Un array de dependencias (opcional, pero muy importante).

El Array de dependencias

El punto más interesante (y la mayoría de los bugs) de useEffect reside en su segundo argumento. Dependiendo de qué le pasemos en el array, el efecto se comportará de forma distinta.

Si omitimos el segundo argumento por completo, el efecto se ejecutará después de CADA renderizado.

useEffect(() => {
  console.log("Me ejecuto siempre que el componente se pinte");
}); 
Copied!

Cuidado con esto. Si dentro de este efecto modificáis el estado (useState), provocaréis un renderizado nuevo, que disparará el efecto de nuevo, que cambiará el estado… creando un bucle infinito que colgará el navegador.

Si pasamos un array vacío [], React solo lo ejecutará una única vez, justo después del primer renderizado.

Básicamente le estamos diciendo a React “Este efecto no depende de ningún valor de las props o el estado”.

useEffect(() => {
  console.log("Me ejecuto solo una vez al arrancar (Mount)");
  
  // Ideal para llamadas a API iniciales
  fetch('/api/usuarios').then(data => ...);
}, []); // <--- Array vacío
Copied!

Si ponemos variables dentro (por ejemplo, [prop1, prop2]), el efecto se ejecutará al montar y además cada vez que alguna de esas variables cambie de valor.

const [id, setId] = useState(1);

useEffect(() => {
  console.log(`El ID ha cambiado a ${id}, pidiendo nuevos datos...`);
  // Se ejecuta al inicio y cada vez que 'id' cambie
}, [id]); 
Copied!

React hace una comparación superficial (Object.is) de las dependencias. Si el valor es diferente al del renderizado anterior, dispara el efecto.

La función de limpieza

A veces, los efectos crean “suciedad” persistente. Por ejemplo, si creamos un setInterval o nos suscribimos a un evento global con addEventListener, esos procesos seguirán vivos aunque el componente desaparezca de la pantalla, provocando fugas de memoria.

Para evitar esto, useEffect nos permite devolver una función de limpieza.

React ejecutará esta función en dos momentos:

  1. Antes de ejecutar el efecto de nuevo
  2. Cuando el componente se desmonta
useEffect(() => {
  // 1. Setup
  const handleResize = () => console.log(window.innerWidth);
  window.addEventListener('resize', handleResize);

  // 2. Cleanup (return function)
  return () => {
    console.log("Limpiando el evento...");
    window.removeEventListener('resize', handleResize);
  };
}, []);
Copied!

Race conditions

Uno de los problemas más comunes al hacer peticiones de datos (Data Fetching) dentro de un useEffect son las Condiciones de Carrera.

Imaginad este escenario:

  1. El usuario selecciona el “Id 1”, el efecto pide datos *(tarda 3 segundos en llegar).
  2. El usuario cambia rápido al “Id 2”, el efecto pide los datos (tardan 0.5segundo en llegar).
  3. Llegan los datos del Id 2 y se muestran.
  4. Llegan los datos del Id 1 (porque era más lento) y sobrescriben a los del 2

El usuario está viendo que ha seleccionado el 2, pero ve la información del 1.

Para solucionar esto, usamos un patrón con una variable booleana ignore (o cancelled) dentro del efecto:

useEffect(() => {
  let ignore = false; // Flag de control

  async function fetchDatos() {
    const json = await obtenerDatos(id);
    // Solo actualizamos si este efecto sigue siendo el "válido"
    if (!ignore) {
      setData(json);
    }
  }

  fetchDatos();

  // Si el componente se desmonta o el id cambia antes de terminar,
  // ponemos ignore a true.
  return () => {
    ignore = true;
  };
}, [id]);
Copied!

Con esto, cuando el “Id 1” termine de cargar, su función de limpieza ya se habrá ejecutado (porque cambiamos al Id 2), ignore será true, y la actualización de estado se ignorará.

¿Cuándo NO usar useEffect?

No lo uséis para transformar datos

Si tenéis firstName y lastName y queréis fullName, no uséis un efecto para hacer setFullName. Calculadlo directamente en el cuerpo del componente.

// ❌ Mal
const [nombre, setNombre] = useState('Luis');
const [apellido, setApellido] = useState('Llamas');
const [completo, setCompleto] = useState('');

useEffect(() => {
  setCompleto(nombre + ' ' + apellido);
}, [nombre, apellido]);

// ✅ Bien (Estado derivado)
const completo = nombre + ' ' + apellido;
Copied!
No lo uséis para manejar eventos de usuario

Si queréis enviar un formulario al hacer clic, hacedlo en el onClick o onSubmit, no en un efecto que escuche cuando cambia el estado. La lógica debe estar donde ocurre la acción.