Banner keyboard

Andrés D'Amelio

Desarrollador web Front-end

Título

Ingeniero en informática

Ubicación

Venezuela

React Hooks: Guía useEffect

Publicado el 13 de abril de 2022

En el artículo anterior conocimos un poco sobre la teoría de hooks, y hablamos sobre el hook useState y sus diversos usos. En esta ocasión vamos a conocer el hook useEffect, y estudiaremos diversos casos de uso en nuestros proyectos.


Para entender esto mejor debemos empezar desde lo más básico, y es qué hace React, lo principal es representar nuestro HTML en el DOM, lo que se conoce como efecto primario, React toma el HTML y lo renderiza, pero en ocasiones necesitamos realizar acciones más allá del renderizado. Estas acciones se conocen como efectos secundarios, estos no deberían en ningún momento ocurrir dentro del renderizado. Si te preguntas ¿Por qué? La respuesta es que el proceso de renderizado de React, debe ser puro, es decir, debe devolver su contenido y no cambiar ninguna variable creada antes del renderizado.


Ahora, estos efectos secundarios tienen que ocurrir en algún momento, efectos como obtener datos de una API, utilizar alguna APi del navegador, etc. Es por esto que React nos ofrece un hook especial que nos permite ejecutar estos efectos secundarios, quedando así aislados de la lógica de renderizado de React, este hook es useEffect


En la documentación oficial de React, encontramos lo siguiente “El Hook de efecto  te permite llevar a cabo efectos secundarios en componentes funcionales:”


Estos efectos secundarios pueden llevar a un nuevo renderizado. Si el efecto cambia el estado se produce una nueva renderización, en caso contrario no influye. useEffect puede verse como una combinación de componenteDidMount, componenteDidUpdate y componenteWillUnmount pertenecientes al ciclo de vida de los componentes de clases en React.


Para utilizar este hook debemos extraerlo desde react de la siguiente manera:


import { useEffect } from 'react';

Con esto podemos utilizarlo dentro de nuestro componente. Es importante resaltar que podemos usar este hook múltiples veces en un solo componente, esto depende de nuestras necesidades.


Estructura del useEffect


El hook useEffect tiene la siguiente estructura


useEffect(callback, [dependencies]);

Como se puede ver, este hook recibe dos parámetros, uno de ellos opcional, veamos un poco cuál es la función de estos


  • callback: Esta función nos va a permitir definir nuestros efectos secundarios, todas aquellas acciones que queremos realizar luego del renderizado. Adicional a esto, podemos limpiar estos efectos al desmontarse nuestro componente, más adelante hablaremos de esto.
  • dependencies: Este parámetro es opcional, y nos permite condicionar nuestros efectos secundarios, dentro de esta matriz podemos agregar ciertas dependencias que hacen que nuestros efectos se ejecuten nuevamente en el momentos que estas cambien.

Usos del useEffect


Tomando en cuenta lo dicho anteriormente sobre los parámetros que recibe el hook, veamos cada caso de uso que podemos tener en nuestro código.


useEffect sin dependencia


Si no pasamos la matriz de dependencias a nuestro hook de efecto, el callback se ejecutará en cada renderizado, es decir, cada vez que ocurra un nuevo renderizado este efecto se ejecutará. Es muy poco común utilizarlo de esta forma, una de las principales razones es que puede generar ciclos infinitos en nuestra aplicación. Supongamos que tenemos un componente con el siguiente código:


import {useState, useEffect } from 'react';

const MyComponent = () => {
    const [message, setMessage] = useState('Hola mundo');

    useEffect(() => { 
        setMessage('Hola de nuevo')
    });

    return <h1>{message}</h1>;
}

export default MyComponent;

Al llamar a este componente tendremos un ciclo infinito, como vemos tenemos un estado inicial ‘Hola mundo’ con el cual empieza el renderizado del componente, luego de terminar el renderizado se ejecuta nuestro efecto secundario, donde se asigna un nuevo valor a nuestra variable de estado message, cuando ocurre un cambio de estado nuestro componente vuelve a renderizarse, lo que hace que nuestro efecto se ejecute nuevamente, creando así un ciclo que nos llevaría a tener el siguiente error:


Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.


Esto es un comportamiento no deseado en nuestra aplicación, por lo que hace que usar el hook de efecto de esta manera no sea una buena opción. Veamos cómo podemos solucionar este error usando nuestra matriz de dependencias. Encuentra el código del ejemplo acá.


useEffect con matriz de dependencias vacía


Este es un caso de uso muy común, si queremos que ocurra un efecto secundario una sola vez, pasamos nuestra matriz de dependencia vacía, lo que hace que al terminar el renderizado se ejecuten nuestros efectos secundarios una sola vez. Veamos como sería con el ejemplo anterior:


import {useState, useEffect } from 'react';

const MyComponent = () => {
    const [message, setMessage] = useState('Hola mundo');

    useEffect(() => {
      setMessage('Hola de nuevo')
    }, []);

    return <h1>{message}</h1>;
}

export default MyComponent;

Como podemos ver, nuestro código es casi idéntico a diferencia de que ahora nuestro hook de efecto recibe como parámetro una matriz vacía. Esto evita que ocurran bucles infinitos. Este caso es uno de los mas usados. Un ejemplo de caso de uso sería una llamada a una API para obtener datos, normalmente queremos que la petición se realice una sola vez. Supongamos que queremos obtener un listado de usuarios para usar en nuestro componente.


import {useState, useEffect } from 'react';

const MyComponent = () => {
    const [users, setUsers] = useState([]);
    const [load, setLoad] = useState(false)
    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(result => {
                setLoad(true)
                setUsers(result)
            })
    }, []);

    return load ? (
            <ul>
                {users.map((user) => {
                    return <li key={user.id}>{user.name}</li>
                })}
            </ul>
        ) : (
            <h2>Cargando datos...</h2>
        )
}

export default MyComponent;

En este caso nuestro componente en su renderizado inicial mostrará en pantalla el texto ‘Cargando datos’, luego de terminar el renderizado, empieza la ejecución de nuestros efectos secundarios, en este caso tenemos una llamada a una API para obtener un listado de usuarios, luego de obtener los datos de la API, actualizamos nuestras variables de estado, lo que hace que nuestro componente se vuelva a renderizar, mostrando de esta manera los datos obtenidos de nuestra API. Encuentra el código de los ejemplos anteriores primer ejemplo y segundo ejemplo


useEffect condicionado por dependencias


Nuestro hook de efecto se ejecuta luego del renderizado, si tenemos nuestra matriz de dependencias vacía, este se ejecutará una sola vez, pero en ocasiones queremos que nuestros efectos se ejecuten luego de que ocurra algún cambio en nuestro componente, ya sea que cambio nuestro state, o que haya cambiado algún prop recibido, etc. Podemos controlar la ejecución de nuestros efectos y así permitir que se ejecuten nuevamente cuando cambie algunas de las dependencias pasadas en el matriz. Después de cada renderizado React procede a verificar si alguna dependencia cambió, si este es el caso ejecuta el efecto nuevamente. Veamos un ejemplo donde ejecutaremos nuestro efecto luego de que nuestro estado cambie:


import { useEffect, useState } from "react";

const MyComponent = () => {
  const [id, setId] = useState(1);
  const [quote, setQuote] = useState(null);
  const [load, setLoad] = useState(false);

  useEffect(() => {
    setLoad(false);
    fetch(`https://www.breakingbadapi.com/api/quotes/${id}`)
      .then((response) => response.json())
      .then((quote) => {
        setQuote(quote[0]);
        setLoad(true);
      });
  }, [id]);

  return load ? (
    <div>
      <h2>{quote.quote}</h2>
      <p>Autor: {quote.author}</p>

      <button onClick={() => setId((prev) => prev + 1)}>Siguiente cita</button>
    </div>
  ) : (
    <h2>Cargando citas...</h2>
  );
};

export default MyComponent;

Para este ejemplo queremos mostrar como funciona la dependencia en el hook de efecto, en este caso queremos obtener citas de una API, y las obtendremos por Id, partiendo desde el id 1 que es nuestro estado inicial, luego de renderizar se ejecuta nuestro efecto y trae los datos de la cita con id 1, se produce un nuevo renderizado donde se muestra los valores, y adicional a eso mostramos un botón para obtener la siguiente cita, este botón básicamente lo que hace es que al ser presionado modifica nuestra variable de estado id, al cambiar el estado de nuestro componente se produce un nuevo renderizado y es donde React verifica si nuestras dependencias han cambiado, al notar que si ocurrió un cambio, procede a ejecutar nuevamente nuestros efectos, que en este caso es buscar en la API la cita con el id actualizado. Puedes encontrar el código del ejemplo acá.


Es importante resaltar que podemos pasar tantas dependencias sean necesarias, y no solo variables de estado, también podemos pasar props, podemos tener lo siguiente:


useEffect(() => {
    // código a ejecutas
}, [state1, state2,..., stateN, props]);

Todos estos son los diferentes casos de uso que tenemos para hook de efecto useEffect, podemos usar cualquier caso según nuestras necesidades, adicional podemos tener múltiples useEffect, incluso el equipo de react recomienda usar varios useEffect para separar conceptos, es decir, no agrupar lógica que no este relacionada en nuestra aplicación. Supongamos que queremos consultar a una API, pero adicional a esto queremos agregar un evento a nuestro documento, estos dos casos no están relacionados, por lo que se aconseja no agruparlos, veamos como sería la implementación:


import { useEffect, useState } from 'react';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json())
      .then((data) => setData(data));
  }, []);

  useEffect(() => {
    window.addEventListener('resize', () => {
      // codigo 
    });
  }, []);
  

  // return HTML/JSX 
}

Limpieza del useEffect


Este es un punto muy importante cuando usamos useEffect, si queremos cuidar el rendimiento o comportamientos inesperados en nuestra aplicación debemos limpiar los efectos secundarios. Hasta ahora hemos visto cómo funciona el hook de efecto, pero no sabemos que pasa después que nuestro componente es desmontado, o cuando ocurre una nueva ejecución del hook. En algunos casos los efectos secundarios pueden seguir ejecutándose aún cuando el componente se ha desmontado.


Para limpiar un efecto secundario, el callback que recibe como primer parámetro el useEffect debe de retornar una función, dentro de esta función retornada debe ir toda nuestra lógica de limpieza. Veamos como es la estructura de useEffect con la limpieza:


useEffect(() => {
    // Logica de efectos secundarios
    return () => {
        // Lógica de limpieza de efectos secundarios
    }
}, []);

Veamos un ejemplo:


//MyComponent.js 
import { useEffect, useState } from "react";

const MyComponent = () => {
  const [screeSize, setScreenSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const resize = () => {
      console.log("ejecutando resize");
      setScreenSize({ width: window.innerWidth, height: window.innerHeight });
    };
    window.addEventListener("resize", resize);
  }, []);

  return (
    <div>
      <h2>Screen Size</h2>
      <p>Width: {screeSize.width}</p>
      <p>Height: {screeSize.height}</p>
    </div>
  );
};

export default MyComponent;

// App.js
import MyComponent from "./MyComponent";
import { useState } from "react";
export default function App() {
  const [show, setShow] = useState(true);
  return (
    <div>
      {show && <MyComponent />}
      <button onClick={() => setShow(!show)}>
        {show ? "Ocultar" : "Mostrar"}
      </button>
    </div>
  );
}

Como vemos en el ejemplo tenemos un componente que es mostrado condicionalmente, este componente agrega a la ventana el evento resize para ir mostrando el alto y ancho de la pantalla. Como no tenemos una limpieza en nuestro hook de efecto al desmontarse el componente el efecto secundario seguirá ejecutándose, y a medida que se monta y desmonta va agregando más eventos resize a la ventana. Esto se puede comprobar pulsando en varias ocasiones el botón de mostrar/ocultar y ver el número de impresiones que se hacen al cambiar el tamaño de la pantalla. En este enlace te dejo el código.


Para evitar esto, debemos retornar la función de limpieza, donde removemos el evento asociado, nuestro useEffect quedaría de la siguiente forma:


useEffect(() => {
  const resize = () => {
    console.log("ejecutando resize");
    setScreenSize({ width: window.innerWidth, height: window.innerHeight });
  };
  window.addEventListener("resize", resize);

    return () => {
        window.removeEventListener("resize", resize);
    }
}, []);

El resultado lo puedes ver en el siguiente enlace.


Conclusión


En esta ocasión conocimos al hook useEffect que juega un papel muy importante en nuestras aplicaciones, el hecho de ejecutar efectos secundarios en nuestros componentes nos da un plus a la hora de trabajar con React. Con esto espero que hayas aprendido y comprendido todo su funcionamiento y que los puedas usar en tus aplicaciones. Si tienes alguna duda no dudes en dejar tu comentario y te responderé en la brevedad posible.


Si te gusto este artículo puedes compartirlo cons tus amigos o en tus redes sociales, y así llegar a mas personas.