Hook useEffect, utilizando el Hook de efecto de forma correcta

Hook useEffect, utilizando el Hook de efecto de forma correcta
Juan Carlos G2020-09-22

Hola viajero web, me da mucho gusto tenerte aquí de nuevo, hoy vamos a revisar el funcionamiento del Hook useEffect, uno de los Hooks que más vas a utilizar cuando trabajes con componentes funcionales.

Y si te gusta este curso gratuito de React JS no olvides en compartir en tus redes sociales.

 

📌 Suscríbete a mi canal y activa la campanita para que no te pierdas ningún video 🤘

 

¿Qué aprenderás de useEffect?

  • Comenzaremos con la declaración general de este Hook.
  • Avanzaremos revisando el como es que con useEffect podemos emular los ciclos de vida:
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount
  • Analizaremos el por que deberías separar procesos en varios useEffect.
  • Revisaremos como mejorar el rendimiento de nuestra aplicación, haciendo que nuestro Hook de efecto solo se ejecute cuando haya un cambio en una variable de estado especifica.

 

 

Repositorio:

  • Puedes descargar el código dando clic aquí.
  • O puedes clonar el repositorio y cambiarte a la rama "useeffect".

 

git clone https://github.com/EWebik/react-js.git
git checkout useeffect

 

¿Qué es el Hook useEffect?

El Hook useEffect es un Hook de efecto que nos permite ejecutar tareas secundaria dentro de nuestros componentes funcionales, por ejemplo, podemos emular los métodos de ciclo de vida, sin la necesidad de utilizar componentes de clase.

 //Declaración
 useEffect(()=>{},[]);

 

¿Para qué sirve useEffect?

Bien, si has seguido este curso seguramente ya tomaste la clase del state and lifecycle React, donde vimos el manejo del state y revisamos algunos métodos de ciclo de vida, solo que los aplicamos utilizando componentes de clase.

Con useEffect podemos emular algunos métodos de ciclo de vida dentro de componentes funcionales y aplicar la lógica que requiera nuestro proyecto, por ejemplo, podemos emular:

  • componentDidMount, se ejecuta cuando el componente es montado
  • componentDidUpdate, se ejecuta cuando el componente sufre un cambio en su state o props.
  • componentWillUnmount, se ejecuta cuando el componente es desmontado.

Por lo tanto, al utilizar este Hook lo que hacemos es decirle a React que nuestro componente debe llevar acabo algo después de renderizase, ejecutando los efectos referentes a:

  • componentDidMount y componentDidUpdate

Pero también le podemos indicar que haga algo cuando el componente se ha desmontado, a través del efecto:

  • componentWillUnmount

 

Emulando los ciclos de vida (lifecycle) con useEffect

En el siguiente código se ilustra como emular los métodos de ciclo de vida utilizando Hooks en un componente funcional.

useEffect(()=>{
    //En el cuerpo de la función se emulan componentDidMount y componentDidUpdate
    console.log("componentDidMount");
    console.log("componentDidUpdate");

    return(
       //El return de useEffect es el equivalente a componentWillUnmount
       console.log("componentWillUnmount");
    )
});

 

¿Cómo utilizar useEffect?

Hay dos cosas particulares en las que este Hook es útil:

  • Nuestro proceso no necesita un saneamiento.
  • Y cuando nuestro proceso requiere de un saneamiento.

Pero ¿A qué se refiere saneamiento o no saneamiento? 🤪 Quizá parezca algo rara la frase o poco relacionada al tema de React, pero no lo es y te lo voy a explicar a continuación.

 

Recomendaciones al utilizar useEffect

Para comenzar a practicar veremos algunas recomendaciones que la misma documentación oficial de la librería de React nos recomienda y nos aconseja utilizar.

 

1. Puedes utilizar varios useEffect para separar procesos

Esto se refiere a que puedes usar más de un Hook de efecto para separar lógica que no esta relacionada, pero que se deben de ejecutar ya sea en al montaje, en la actualización o en el desmontaje del componente.

Un ejemplo de esto es aquel proceso donde no necesitamos invocar el método componentWillUnmount, a esto se le llama sin saneamiento.

 

Utilizando useEffect sin saneamiento

Esto se refiere a que nuestro componente solo ejecuta, componentDidMount y componentDidUpdate, y no requiere que hagamos nada al ser desmontado, ya que, no deja nada cargado en memoria.

Pero para que te quede más claro, veamos el siguiente ejemplo:

 

Ejemplo, crear aplicación que obtenga la posición del usuario mediante el navegador y al mismo tiempo realice un contador.

En este ejemplo tenemos los siguientes objetivos principales:

  1. Por un lado crear el proceso para obtener la posición del usuario.
  2. Después crear el proceso que incrementara la cuenta.
  3. Ambos procesos deben estar en Hooks diferentes para cumplir con la primera recomendación.

Para cumplir con el primer objetivo vamos a utilizar navigator.geolocation el cual es una función de JavaScript que nos permite obtener la posición de un usuario a través del navegador.

  useEffect(()=>{
    //Emular los métodos de ciclo de vida
    //componentDidMount
    //componentDidUpdate

    console.log("componentDidMount");
    console.log("componentDidUpdate");

    //Posición
    if(navigator.geolocation){
      navigator.geolocation.getCurrentPosition((pos)=>{
        setLatitud(pos.coords.latitude.toFixed(0));
        setLongitud(pos.coords.longitude.toFixed(0));
      });
    }else{
      console.log("El navegador no soporta la geolocalización");
    }  
  });

Donde navigator.geolocation.getCurrentPosition regresa una función con la posición del navegador de usuario. No obstante, lo más importantes es que en este useEffect solo esta el código referente a la localización, ahora debemos crear el del contador.

 useEffect(()=>{
    //Contador
    const intervalo = setInterval(() => {
      console.log("Intervalo...")
      setContador(contador + 1);
    }, 1000);

    return ()=>{
      //componentWillUnmount
      console.log("componentWillUnmount");
      clearInterval(intervalo);
    }
  })

Como puedes observar, en este otro Hook solo inicializamos el intervalo y lo eliminamos cada vez que el componente es desmontado.

Con esto ya hemos cumplido con el primer consejo, no obstante, en este punto ambos efectos no son independientes y hacen que el navegador trabaje más de la cuenta, ya que, cada vez que la variable de estado "contador" cambie debido a setContador el componente se refrescará y se volverán a ejecutar ambos Hooks. En el siguiente apartado te explico como evitar esto.

 

Utilizando useEffect con saneamiento

Bien este concepto hace referencia a que nuestro componente requiere llevar a cabo una limpieza cuando el componente es desmontado (componentWillUnmount), esta situación se puede dar por muchas razones:

  • Hemos realizado una suscripción con alguna API.
  • Tenemos corriendo procesos como intervalos que se ejecutan cada determinado tiempo.
  • Etc.

📌 NOTA: Si requieres que tu función de saneamiento se ejecute una sola vez, debes pasar un array vació [] como segundo parámetro, ya que, con esto le indicas a React que el efecto no depende de ninguna variable de estado o props y no requiere volver a ejecutarse.

 useEffect(()=>{
    //Contador
    const intervalo = setInterval(() => {
      console.log("Intervalo...")
      setContador(contador + 1);
    }, 1000);

    return ()=>{
      //componentWillUnmount
      console.log("componentWillUnmount");
      clearInterval(intervalo);
    }
  })

En el código anterior vemos claramente el saneamiento, ya que, en el cuerpo de la función inicializamos el intervalo y en el return del useEffect lo eliminamos, con esto logramos que no se queden intervalos ejecuntándose inecesariamente. Aun así, seguimos ejecutando ambos Hooks en cada cambio del state contador, veamos como optimizar nuestra aplicación y mejor el rendimiento.

 

2. Mejora el rendimiento omitiendo efectos

El segundo consejo que nos recomienda React es, divide y vencerás, supongamos que necesitas hacer algo cuando tus variables de estado cambien, pues para ello, lo que puede hacer es utilizar un Hook de efecto que escuche a una variable de estado en particular o propiedad.

Así cada vez que haya una actualización, cada uno reaccionará a la variable de estado que esta escuchando, si esta no ha sufrido ningún cambio el proceso no se ejecutará y no necesitará recursos del navegador.

Si un Hook de efecto no se ejecuta, optimizarás los recursos y tu aplicación correrá mejor a la vista de tus usuarios.

Retomemos nuestro ejemplo y veamos el componente que contiene a nuestros dos Hooks que hemos venido desarrollando.

const Hijo = (props)=>{
  const [contador, setContador] = useState(0);
  const [latitud, setLatitud] = useState(0);
  const [longitud, setLongitud] = useState(0);

  //Consejo 1 - Separar procesos
  //Consejo 2 - Mejorar rendimiento separando efectos

  //Ejemplo donde no requiere saneamiento
  useEffect(()=>{
    //Emular los métodos de ciclo de vida
    //componentDidMount
    //componentDidUpdate

    console.log("componentDidMount");
    console.log("componentDidUpdate");

    //Posición
    if(navigator.geolocation){
      navigator.geolocation.getCurrentPosition((pos)=>{
        setLatitud(pos.coords.latitude.toFixed(0));
        setLongitud(pos.coords.longitude.toFixed(0));
      });
    }else{
      console.log("El navegador no soporta la geolocalización");
    }  
  },[latitud,longitud]);

  //Ejemplo donde si requiere saneamiento
  useEffect(()=>{
    //Contador
    const intervalo = setInterval(() => {
      console.log("Intervalo...")
      setContador(contador + 1);
    }, 1000);

    return ()=>{
      //componentWillUnmount
      console.log("componentWillUnmount");
      clearInterval(intervalo);
    }
  },[contador])

  return(
    <div>
       <p>{contador}</p>
       <p>{`Lat: ${latitud}, Lon: ${longitud}`}</p>
    </div>
  )
}

En el código anterior hemos optimizado nuestro código ya que hemos hecho que cada Hook sea independiente a la ejecución del otro y solo se lanzará cuando realmente sea necesario, para lograr esto lo que hicimos fue utilizar el segundo argumento que recibe useEffect, el cual es una arreglo que recibe todas las variables de estado o propiedades que queremos escuchar para ejecutor el Hook.

  //Ejemplo donde no requiere saneamiento
  useEffect(()=>{
       .....
  }, [latitud, longitud] )

  //Ejemplo donde si requiere saneamiento
  useEffect(()=>{
      .......
  },[contador])

📌 NOTA: Si optimizas tu aplicación de esta manera, no olvides agregar dentro de los [] todos los valores que influyen en el ámbito de tu función de efecto, tanto variables de estado como las props, ya que de lo contrario estarías referenciando hacia datos obsoletos.

 

Código completo del ejemplo

Bien, espero no haberte confundido, ya que, créeme que trate de explicarme lo mejor posible, si te ha quedado alguna duda, por favor, mira el vídeo ya que en el te muestro paso a paso todo esto y podrás ver funcionando el código.

 

import React,{useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

//Hook de efecto o useEffect

const Hijo = (props)=>{
  const [contador, setContador] = useState(0);
  const [latitud, setLatitud] = useState(0);
  const [longitud, setLongitud] = useState(0);

  //Consejo 1 - Separar procesos
  //Consejo 2 - Mejorar rendimiento separando efectos

  //Ejemplo donde no requiere saneamiento
  useEffect(()=>{
    //Emular los métodos de ciclo de vida
    //componentDidMount
    //componentDidUpdate

    console.log("componentDidMount");
    console.log("componentDidUpdate");

    //Posición
    if(navigator.geolocation){
      navigator.geolocation.getCurrentPosition((pos)=>{
        setLatitud(pos.coords.latitude.toFixed(0));
        setLongitud(pos.coords.longitude.toFixed(0));
      });
    }else{
      console.log("El navegador no soporta la geolocalización");
    }  
  },[latitud,longitud]);

  //Ejemplo donde si requiere saneamiento
  useEffect(()=>{
    //Contador
    const intervalo = setInterval(() => {
      console.log("Intervalo...")
      setContador(contador + 1);
    }, 1000);

    return ()=>{
      //componentWillUnmount
      console.log("componentWillUnmount");
      clearInterval(intervalo);
    }
  },[contador])

  return(
    <div>
       <p>{contador}</p>
       <p>{`Lat: ${latitud}, Lon: ${longitud}`}</p>
    </div>
  )
}

const Componente = (props)=>{
  const [verHijo, setVerHijo] = useState(true);
  
  //Declaración
  //useEffect(()=>{},[]);

  return(
    <div className="padre">
     <h1>{"Hook useEffect by EWebik"}</h1>
     {
       verHijo ? (<Hijo />):("")
     }
     <br/>
     <button onClick={()=>{
          setVerHijo(!verHijo);
     }}>{verHijo ? "Ocultar":"Ver"}</button>
    </div>
  )
}

ReactDOM.render(
  <Componente />,
  document.getElementById('root')
);

 

Excelente, espero que este post te haya sido de gran ayuda y que te haya quedado claro como utilizar el Hook de efecto useEffect, recuerda visitar las demás clases de este curso y si quieres apoyarme solo debes compartir en redes sociales y suscribirte al boletín y a mi canal en YouTube, nos vemos en el siguiente post.

 


Juan Carlos G

Electrónica y diseño web

Durante años he desarrollado plataformas dedicadas al rastreo satelital y varios sitios web que se encuentran en la primera página de Google, y hoy quiero compartir contigo lo que se en tecnologías como Node JS, PHP, C# y Bases de datos, si quieres apoyarme sígueme en mis redes sociales y suscríbete a mi canal de YouTube.

Puedes seguirme en mis redes