useEffect, el hook de efecto de React - Ciclos de vidas en componentes funcionales

  Diego Artiles   •     12 Min

Además del useState, el hook useEffect es uno de los más usados en React. Anteriormente ya explicamos como funciona el useState y como usarlo con arrays y objetos, y ahora es el momento de este hook 🤗

Ciclos de vidas en componentes funcionales

Antes en React (versiones inferiores a la 16.8) disponíamos de varias funciones heredadas de la clase Component, que nos permitían acceder a diferentes momentos del ciclo de vida de nuestro componente (montaje, actualización y desmontaje) .

Ahora con la llegada de este hook, se pueden acceder a los eventos más usados desde una única función, tales son componentDidMount, componentDidUpdate y componentWillUnmount

¿Cómo se usa el useEffect?

Como bien les conté, el useEffect unifica 3 métodos asociados al ciclo de vida de nuestro componente, así que ¿Cómo diferenciamos cada uno de estos métodos?

Este hook al igual que todos son funciones, y en este caso recibe dos parámetros, el primero corresponde a un callback o función y el segundo un arreglo de dependencias.

El callback no recibe ningún parámetro solamente nos sirve para ejecutar código en el momento de que se produzca el efecto deseado.

Por otro lado, el segundo parámetro es opcional y dependiendo de su valor el efecto se ejecutará.

import React, { useEffect } from 'react'

useEffect(() => { /* Your code here... */ });

Momentos de ejecución según el valor del segundo parámetro del useEffect

Pasemos a descubrir los 3 posibles casos en los que se puede ejecutar este hook según el valor del segundo parámetro. 🤓

  • Sin valor: cuando omitimos este parámetro, el efecto se producirá en el primer renderizado y en cada uno de los subsecuentes (cuando se produce un cambio de estado o las props cambian). Podemos decir que en este caso están combinados los métodos componentDidMount y componentDidUpdate

  • Arreglo vacío [ ]: cuando le pasamos este valor, el efecto se producirá únicamente en el primer renderizado y es equivalente al método componentDidMount. Y esto es debido a que el efecto se produce siempre y cuando el valor dentro del arreglo [ ] cambie, y como en este caso al no recibir nada, React ejecuta el efecto una única vez.

  • Arreglo con dependencias: los posibles valores de este arreglo pueden ser variables asociadas a un estado o props del componente, según sea el caso el hook se ejecutará cada vez que su(s) dependencia(s) cambie(n).

    Podemos pasar la cantidad de dependencias que queramos, pero realmente no es muy recomendado (más que nada por razones de performance) y se opta por usar varios useEffect con diferentes dependencias.

    Este caso es similar al método componentDidUpdate, a excepción de que sólo se ejecutará cuando las dependendencias cambien y no ante cada cambio o reenderizado como si ocurre con este método de las componentes de clases.

Como te habrás dado cuenta no mencioné el método componentWillUnmount, y es porque no solamente tiene que ver con el valor del segundo parámetro del useEffect, si no que hace falta unos pequeños detalles que ya te contaré 😈

A continuación te dejo las comparaciones de componentDidMount, componentDidUpdate, componentWillUnmount con el useEffect

Ciclos de vida en componentes de tipo clase vs componentes de tipo función

Si llevas mucho tiempo trabajando con los componentes de clases, te servirá mucho esta información para saber las equivalencias de los métodos antes mencionados y su uso a través del useEffect.

componentDidMount

Si nos vamos a la traducción literal del nombre de este método al español es "El componente se montó", y sí, este es el preciso momento en el que se ejecuta este método, justo y únicamente luego de hacer el primer renderizado del componente.

Podemos decir que hace la misma función que el método OnInit de Angular 😎

Bien, pasemos a como se accede a este momento del ciclo de vida con componentes de clases y como lo haríamos con los componentes funcionales

import React from "react";
class App extends React.Component {
  state = { name: "Diego" };

  componentDidMount() { // Método ejecutado en el primer renderizado
    console.log("Se inicializó el componente por primera vez");
  }

  render() {
    return <h1>Mi nombre es {this.state.name}</h1>;
  }
}
export default App;
Accediendo al momento del primer renderizado con componentes de clase
import React, { useEffect, useState } from "react";

const App = () => {
  const [state] = useState({ name: "Diego" });
    
  useEffect(() => {
    console.log("Se inicializó el componente por primera vez");
  }, []); // Método ejecutado en el primer renderizado
    
  return <h1> Hello {state.mensaje}</h1>;
};

export default App;
Accediendo al momento del primer renderizado con componentes funcionales

Como verán el segundo parámetro de este hook juega un papel muy importante para decidir cuando se ejecutará el efecto, tal cual como mencioné anteriormente en el caso de querer ejecutar código al momento del primer renderizado, basta con pasarle un array vacío al segundo parámetro 😁

Entiéndase primer renderizado como el momento justo cuando React monta un componente en el DOM, es decir, cuando nosotros veamos nuestro componente en el DOM, posteriormente se ejecutará por única vez el efecto.

componentDidUpdate

Su traducción literal al español es "el componente se actualizó", creo que el nombre se explica por si solo.

Este método se ejecutará cada vez que se produzca un cambio de estado o de props e independientemente de a quien pertenezca el cambio, es decir, no importa si cambio una prop o un state sea cual sea el caso se ejecutará este método.

Si nos vamos a Angular este método es similar a OnChanges 😎

Comparémoslo con el useEffect

Supongamos que queremos registrar la cantidad de veces que ha sido presionado un botón ante cada cambio:

import React from "react";

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      clicks: 0
    };
  }

  componentDidUpdate() {
    // Ante cada cambio mostramos por consola la cantidad de veces que ha sido presionado el boton
    console.log(
      "Cantidad de veces que ha sido presionado el botón:",
      this.state.clicks
    );
  }

  render() {
    return (
      <div>
        <p>Este botón ha sido presionado {this.state.clicks} vece(s)</p>
        <button
          onClick={() => this.setState({ clicks: this.state.clicks + 1 })}
        >
          Presioname
        </button>
      </div>
    );
  }
}

export default Counter;
Usando los eventos de las clases en React
import React, { useState, useEffect } from "react";

const Counter = () => {
  const [state, setState] = useState({ clicks: 0 });

  useEffect(() => {
    console.log(
      "Cantidad de veces que ha sido presionado el botón:",
      state.clicks
    );
  });

  return (
    <div>
      <p>Este botón ha sido presionado {state.clicks} vece(s)</p>
      <button onClick={() => setState({ clicks: state.clicks + 1 })}>
        Presioname
      </button>
    </div>
  );
};

export default Counter;
Usando los hooks useState y useEffect

A nivel funcional encontramos una pequeña diferencia, como les comenté anteriormente cuando el useEffect no tiene dependencia se están ejecutando los métodos componentDidMount y componentDidUpdate, por lo cual, en el ejemplo del useEffect tendremos que en el primer renderizado veremos el log en la consola.

Pero este es un detalle menor, lo realmente interesante es cuando queramos registrar por consola solamente cuando cambie la cantidad de clicks, ya que el código que tenemos ahora provocará que se registre ese evento sin importar en donde se produjo el cambio, sean props o estados.

Veamos un ejemplo de como solucionar esto, agregando un botón "B" y registrando únicamente en consola cuando el botón "A" sea presionado. 🙊

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicksA: 0, clicksB: 0 };
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.clicksA !== this.state.clicksA) {
    console.log(
      "Cantidad de veces que ha sido presionado el botón A:",
      this.state.clicksA
    );
    }
  }

  render() {
    return (
    <div>
      <button
        onClick={() => this.setState({ ...this.state, clicksA: this.state.clicksA + 1 })}
      >
        Boton A
      </button>
      <button
        onClick={() => this.setState({ ...this.state, clicksB: this.state.clicksB + 1 })}
      >
        Boton B
      </button>
    </div>
    );
  }
}

export default Counter;
const Counter = () => {
  const [state, setState] = useState({ clicksA: 0, clicksB: 0 });

  useEffect(() => {
    console.log(
      "Cantidad de veces que ha sido presionado el botón A:",
      state.clicksA
    );
  }, [state.clicksA]);

  return (
    <div>
      <button
        onClick={() => setState({ ...state, clicksA: state.clicksA + 1 })}
      >
        Boton A
      </button>
      <button
        onClick={() => setState({ ...state, clicksB: state.clicksB + 1 })}
      >
        Boton B
      </button>
    </div>
  );
};

export default Counter;

Como podrán observar en las componentes de clases tenemos que hacer una diferenciación explicita para decidir cuando registrar en consola las veces que el botón A ha sido presionado a diferencia del useEffect que solo basta con pasarle como dependencia el state.clicksA.

Otra diferencia que existe, es que el useEffect lo podemos usar cuantas veces querramos y el componentDidUpdate solo una vez y todos los cambios pasaran por ese método.

Si aún llegando hasta acá te quedan dudas sobre el funcionamiento y las diferencias de este evento, puedes hacérmelo saber en los comentarios 😊

componentWillUnmount

Bien hasta ahora no hemos explicado la equivalencia de este método con este hook, antes de explicarlo vayamos a la traducción literal al español para saber cuando se ejecuta este evento 😁, tenemos entonces "El componente se desmontará", esto quiere decir que el método se ejecutará justo antes de desmontarse del DOM.

La equivalencia en Angular sería OnDestroy 😎

Uno de los casos más útiles por la que solemos usar este evento, es para cancelar alguna suscripción, para ello supongamos que tenemos en nuestro componente un setInterval que hace "algo" y queremos limpiarlo antes de que nuestro componente se desmonte.

import React from "react";

class Interval extends React.Component {
  constructor(props) {
    super(props);
    this.interval;
  }

  componentDidMount() { // Inicializamos el interval al montarse el componente
    this.interval = setInterval(() => console.log("Algo estoy haciendo"), 1000);
  }

  componentWillUnmount() { //Eliminamos el interval justo antes de desmontarse el componente
    clearInterval(this.interval);
  }

  render() {
    return <p>Componente Interval</p>;
  }
}

export default Interval;
import React, { useEffect } from "react";

const Interval = () => {
  useEffect(() => {
    const interval = setInterval( () => console.log("Algo estoy haciendo"), 1000); // Inicializamos el interval al montarse el componente
    return () => clearInterval(interval); //Eliminamos el interval justo antes de desmontarse el componente
  }, []);

  return <p>Componente Interval</p>;
};

export default Interval;

Como podrán observar dentro del callback del useEffect, estamos retornando además otra función, esta función nos permite ejecutar código justo antes de que el componente se desmonte.

Para acceder al método componentWillUnmount desde el useEffect, además de retornar una función, la dependencia debe ser un arreglo vacío [ ].

Es de buena práctica, eliminar cualquier tipo de evento asincrónico antes de desmontar el componente a fin de mejorar el performance y evitar cualquier problema de volcado de memoria y bugs.

Conclusiones

Bien en resumen, el useEffect es una nueva manera de acceder a los ciclos de vidas de nuestros componentes funcionales e incluso nos permite usar menos líneas de código 🤓. ¿Te gustó este post? ¿Aprendiste algo nuevo? ¿Tienes alguna duda? ¿Quieres que explique algo en particular? Házmelo saber en la sección de comentarios y recuerda seguirme en mis redes sociales 😢

Gracias por leer 😚