martes, 22 de julio de 2025

React: useEffect y su array de dependencias

 ¿Qué es el array de dependencias de useEffect? Evocando al concepto de programación de argumentos de función por valor y por referencia ¿Las dependencias de useEffect son adquiridas por valor o por referencia?

¡Excelente pregunta! Es fundamental comprender el array de dependencias de useEffect para escribir código React eficiente y sin errores.

¿Qué es el Array de Dependencias de useEffect?

El array de dependencias es el segundo argumento opcional que se pasa al hook useEffect. Su propósito principal es decirle a React cuándo debe volver a ejecutar la función de efecto que le has proporcionado.

La función de efecto (el primer argumento de useEffect) se ejecuta:

  1. Después del primer renderizado del componente (equivalente a componentDidMount en componentes de clase).

  2. Después de cada re-renderizado del componente, si alguna de las dependencias en el array ha cambiado desde la última vez que se ejecutó el efecto.

Sintaxis:

JavaScript
useEffect(() => {
  // Tu código de efecto aquí
  // Se ejecutará cuando las dependencias cambien
}, [dependencia1, dependencia2, ...]); // Este es el array de dependencias

Comportamientos clave según el array de dependencias:

  • Sin array de dependencias (omitiéndolo):

    JavaScript
    useEffect(() => {
      // Se ejecuta después de CADA renderizado.
      // ¡Cuidado con los bucles infinitos si actualizas el estado aquí!
    });
    
  • Array de dependencias vacío ([]):

    JavaScript
    useEffect(() => {
      // Se ejecuta SOLO una vez después del primer renderizado (montaje).
      // Útil para suscripciones, llamadas a API iniciales, etc.
    }, []);
    
  • Array con dependencias específicas:

    JavaScript
    useEffect(() => {
      // Se ejecuta después del primer renderizado,
      // y cada vez que 'userId' o 'productId' cambien.
    }, [userId, productId]);
    

¿Las dependencias de useEffect son adquiridas por valor o por referencia?

Para entender esto, es crucial recordar cómo funciona JavaScript con el paso de argumentos a funciones: JavaScript siempre pasa argumentos por valor. Sin embargo, la "valor" de una variable puede ser una copia del valor primitivo o una copia de la referencia a un objeto/array en memoria.

Aplicando esto a las dependencias de useEffect:

  1. Para valores primitivos (números, cadenas de texto, booleanos, null, undefined):

    React compara estos valores por su valor. Si dependencia1 era 5 y ahora es 5 de nuevo, React considera que no ha cambiado. Si era 5 y ahora es 6, sí ha cambiado.

    • Ejemplo:

      JavaScript
      const count = 5;
      useEffect(() => { /* ... */ }, [count]); // Solo se re-ejecuta si 'count' cambia a un valor diferente de 5
      
  2. Para valores no primitivos (objetos, arrays, funciones):

    React compara estos valores por su referencia (dirección de memoria). Si un objeto, un array o una función se crea de nuevo en cada renderizado, incluso si su contenido es idéntico, su referencia en memoria es diferente. React detectará este cambio de referencia y considerará que la dependencia ha cambiado, lo que provocará que el useEffect se re-ejecute.

    • Ejemplo de problema (bucle potencial):

      JavaScript
      function MyComponent() {
        const [data, setData] = useState([]);
        const myObject = { id: 1 }; // myObject es un nuevo objeto en cada render
      
        useEffect(() => {
          // Esto se ejecutará en CADA renderizado porque myObject es una nueva referencia
          console.log("myObject ha cambiado (su referencia)");
          // Si aquí actualizaras el estado, tendrías un bucle
        }, [myObject]); // myObject cambia de referencia en cada render
      
        return <div>...</div>;
      }
      
    • Ejemplo de problema con funciones (bucle potencial):

      JavaScript
      function MyComponent() {
        const [count, setCount] = useState(0);
        const doSomething = () => { // doSomething es una nueva función en cada render
          console.log('Haciendo algo');
        };
      
        useEffect(() => {
          doSomething();
          // Si aquí setCount(count + 1), tendrías un bucle
        }, [doSomething]); // doSomething cambia de referencia en cada render
      
        return <button onClick={() => setCount(count + 1)}>Incrementar</button>;
      }
      

Solución para dependencias no primitivas:

Para evitar re-ejecuciones innecesarias (y posibles bucles infinitos) con objetos, arrays y funciones que no cambian lógicamente pero sí de referencia en cada render, React proporciona hooks de memoización:

  • useCallback: Para funciones. Memoiza la función, asegurando que su referencia sea la misma entre renderizados a menos que sus propias dependencias cambien.

    JavaScript
    const doSomething = useCallback(() => {
      console.log('Haciendo algo');
    }, []); // La función solo se recrea si sus dependencias (vacías en este caso) cambian
    useEffect(() => {
      doSomething();
    }, [doSomething]); // Ahora doSomething es una referencia estable
    
  • useMemo: Para objetos y arrays. Memoiza el valor del objeto o array, asegurando que su referencia sea la misma entre renderizados a menos que sus propias dependencias cambien.

    JavaScript
    const myObject = useMemo(() => ({ id: 1 }), []); // El objeto solo se recrea una vez
    useEffect(() => {
      console.log("myObject ha cambiado (su referencia)");
    }, [myObject]); // Ahora myObject es una referencia estable
    

En resumen, aunque JavaScript pasa por valor, para los tipos de datos complejos (objetos, arrays, funciones), el "valor" que se pasa es una referencia a una ubicación en memoria. React compara estas referencias en el array de dependencias de useEffect. Por lo tanto, es crucial gestionar la estabilidad de estas referencias para controlar cuándo se ejecutan tus efectos.

No hay comentarios: