DEV Community

Cover image for 🔹 `useEffect` – Efectos secundarios y ciclo de vida 2/8
Pedro Alvarado
Pedro Alvarado

Posted on

🔹 `useEffect` – Efectos secundarios y ciclo de vida 2/8

📋 Tabla de contenidos

  1. useState – El fundamento del estado
  2. useEffect – Efectos secundarios y ciclo de vida
  3. useContext – Compartir estado sin props
  4. useReducer – Lógica de Estado Compleja y predecible
  5. useRef – Referencias, Almacenamiento y Escape
  6. useMemo y useCallback – Optimizando el Rendimiento
  7. Custom Hooks – Creando Lógica Reutilizable
  8. Errores Comunes y Soluciones

¿Qué es y para qué sirve?

useEffect conecta tus componentes con sistemas externos. Permite ejecutar "efectos secundarios" después de que el componente se haya renderizado. Un efecto secundario es cualquier código que afecta a algo fuera del propio componente, como:

  • Llamadas a una API para buscar datos.
  • Suscripciones a eventos (ej. window.addEventListener).
  • Manipulación manual del DOM.
  • Timers como setInterval o setTimeout.

useEffect reemplaza a los métodos de ciclo de vida componentDidMount, componentDidUpdate y componentWillUnmount de los componentes de clase.

Sintaxis y el array de dependencias

useEffect(() => {
  // ...tu código del efecto...

  return () => {
    // ...función de limpieza (opcional)...
  };
}, [dependencias]);
Enter fullscreen mode Exit fullscreen mode

La parte más importante de useEffect es el array de dependencias. Controla cuándo se ejecuta el efecto:

  1. [dependencia1, dependencia2]: El efecto se ejecuta solo si alguna de las dependencias ha cambiado desde el último render. Este es el caso más común.
  2. [] (array vacío): El efecto se ejecuta una sola vez, justo después del renderizado inicial (equivalente a componentDidMount). Perfecto para inicializaciones o fetching de datos que no cambian.
  3. No se proporciona el array: El efecto se ejecuta después de cada renderizado. ¡Cuidado! Esto puede causar bucles infinitos si el efecto actualiza el estado.

La función de limpieza (return) es opcional y se ejecuta:

  • Antes de que el efecto se vuelva a ejecutar (si una dependencia cambió).
  • Cuando el componente se desmonta (se elimina de la pantalla).

Es crucial para limpiar suscripciones, timers o listeners y evitar fugas de memoria.


Ejemplo práctico detallado: fetching de datos con estado de carga y error

Este es el caso de uso más común para useEffect. Vamos a buscar datos de un Pokémon y a manejar los estados intermedios.

import React, { useState, useEffect } from 'react';

function PokemonInfo({ pokemonName }) {
  const [pokemon, setPokemon] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!pokemonName) return;

    // Reseteamos los estados al empezar una nueva búsqueda
    setLoading(true);
    setError(null);
    setPokemon(null);

    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`)
      .then(response => {
        if (!response.ok) {
          throw new Error('No se encontró el Pokémon');
        }
        return response.json();
      })
      .then(data => {
        setPokemon(data);
      })
      .catch(error => {
        setError(error.message);
      })
      .finally(() => {
        setLoading(false);
      });

  }, [pokemonName]); // El efecto depende del nombre del Pokémon

  if (loading) return <p>Cargando...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      {pokemon && (
        <>
          <h3>{pokemon.name}</h3>
          <img src={pokemon.sprites.front_default} alt={pokemon.name} />
        </>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

✅ Buenas practicas y patrones comunes

  1. Un efecto, una responsabilidad: Si tienes lógicas no relacionadas, sepáralas en varios useEffect. Uno para fetching, otro para un event listener, etc. Esto hace el código más limpio.
  2. Limpia siempre tus efectos: Si te suscribes a algo, crea un timer o un listener, siempre devuelve una función de limpieza para cancelarlo.
  3. No olvides las dependencias: El linter de React te suele avisar si usas una variable o función dentro del efecto pero no la incluyes en el array. ¡No lo ignores! Omitir dependencias puede causar bugs muy difíciles de rastrear.
  4. Funciones en el array de dependencias: Si una función se usa dentro de useEffect, debe estar en el array. Pero si esa función se redefine en cada render, causará que el efecto se ejecute siempre. La solución es definir la función dentro del useEffect o memorizarla con useCallback.

🚨 Errores comunes y cómo evitarlos

  • Error: El bucle infinito.

    // MAL ❌
    const [count, setCount] = useState(0);
    useEffect(() => {
      // Este efecto cambia el estado, lo que causa un re-render,
      // lo que vuelve a ejecutar el efecto, y así hasta el infinito.
      setCount(count + 1);
    }); // Sin array de dependencias
    
    • Solución: Añade un array de dependencias. Si quieres que se ejecute solo una vez, que sea []. Si quieres que reaccione a un cambio, incluye la dependencia [otraVariable].
  • Error: Datos "viejos" (stale state) por dependencias faltantes.

    // MAL ❌
    const [count, setCount] = useState(0);
    useEffect(() => {
      // Este intervalo siempre "verá" count como 0, porque la función
      // del intervalo se creó en el primer render y nunca se actualizó.
      const intervalId = setInterval(() => {
        console.log(`El contador es: ${count}`);
      }, 1000);
      return () => clearInterval(intervalId);
    }, []); // El array vacío le dice a React: "nunca vuelvas a ejecutar esto"
    
    • Solución 1: Añadir la dependencia.

      // BIEN ✅ (pero recrea el intervalo cada vez que count cambia)
      useEffect(() => { ... }, [count]);
      
    • Solución 2: Usar la actualización funcional (mejor en este caso).

      // EXCELENTE ✅
      // Dentro de `setCount`, React siempre te da el valor más reciente.
      // No necesitamos `count` como dependencia.
      useEffect(() => {
        const intervalId = setInterval(() => {
           setCount(c => c + 1); // usamos la versión más reciente del estado
        }, 1000);
        return () => clearInterval(intervalId);
      }, []);
      

🚀 Retos prácticos

  1. Temporizador con Start/Stop/Reset: Usa useEffect y useState para crear un cronómetro funcional.
  2. Seguir el ratón: Crea un componente que muestre las coordenadas X e Y del ratón en tiempo real. (Pista: window.addEventListener('mousemove', ...), y no olvides limpiar el listener).
  3. Título de la página dinámico: Haz que el título del documento (document.title) se actualice para mostrar el valor de un contador o un input.
  4. Autoguardado: Crea un textarea que guarde su contenido en localStorage automáticamente 2 segundos después de que el usuario deje de escribir.

Top comments (0)