9 - Hook de efecto (función useEffect)

Vimos en un concepto anterior los hook más utilizados que son los de variables de estado, ahora en orden de uso presentaremos los hook de efectos.

Recordemos que los hook solo pueden ser utilizados cuando implementamos componentes funcionales, que es la metodología más actual y propuesta por los creadores de la librería React.

El Hook de efecto permite llevar a cabo efectos secundarios en componentes funcionales, ejemplos de estos efectos son:

  • Actualizaciones manuales del DOM.
  • Suscribir y desuscribir a eventos (ej. addEventListener, removeListener)
  • Peticiones de datos a un servidor (ej. API fetch)

La sintaxis básica la podemos ver en el siguiente código:

import { useEffect } from "react";

function App() {

  useEffect(() => console.log("ejecución de useEffect"))

  return (
    <div>
      Hola Mundo
    </div>
  );
}

export default App

Debemos importar la función:

import { useEffect } from "react";

Y en su sintaxis más sencilla pasamos a la función useEffect una función flecha:

  useEffect(() => console.log("ejecución de useEffect"))

Sintaxis de la función useEffect

  • La función useEffect tiene dos parámetros: el primero una función y el segundo un array cuyos valores serán variables de las que depende nuestro algoritmo que implementa la función que le pasamos (este arreglo es opcional como vemos en el ejemplo codificado)
  • La función useEffect se ejecuta en cada renderizado, inclusive en el primero.
  • Podemos llamar a la función useEffect más de una vez y crear varios hook de efecto.
  • Está diseñado para que si la función que pasamos por parámetro retorna otra función, React va a ejecutar dicha función cuando se desmonte el componente del DOM (normalmente en esta función liberamos recursos como desuscribirse a eventos)

Problema 1

Actualizar en tiempo real el título de la página con los caracteres ingresados en un control input. Para esto debemos acceder directamente al DOM.

Crear con la aplicación create-react-app el proyecto008.

import { useState, useEffect } from 'react';

function App() {

  const [texto, setTexto] = useState("")

  useEffect(() => {document.title = texto}, [texto])

  function cambiar(e) {
    setTexto(e.target.value)
  }

  return (
    <div>
      <p><input type="text" onChange={cambiar} /></p>
      <p>{texto}</p>
    </div>
  );
}

export default App;

Cada vez que escribimos un caracter en el control 'input' se actualiza automáticamente la página con la variable de estado 'texto' y también se ejecuta el hook donde se accede al título de la página mediante 'document':

Hook de tipo useEffect

Importamos las dos funciones que necesitamos para crear un hook de variable de estado y un hook de efecto:

import { useState, useEffect } from 'react';

Llamamos a la función useEffect y le pasamos la función anónima donde actualizamos el título de la página con el valor almacenado en la variable de estado 'dato' (para que la aplicación sea más eficiente pasamos un segundo parámetro a la función useEffect con un vector que contiene la variable de estado):

  useEffect(() => {document.title = texto}, [texto])

Si pasamos un vector vacío como segundo parámetro, luego no se actualizará el título de la página (si en algún caso queremos que se ejecute el hook una única vez indistintamente la cantidad de veces que se renderize la componente puede tener sentido pasar un array vacío, no es el caso actual):

  useEffect(() => {document.title = texto}, [])

Definimos un hook de estado con el contenido ingresado en el control input:

  const [texto, setTexto] = useState("")

Cada vez que se carga o borra un caracter (es decir hay un cambio) se dispara la función 'cambiar':

      <p><input type="text" onChange={cambiar} /></p>

La función 'cambiar' actualiza la variable de estado, lo cual dispara el hook de efecto:

  function cambiar(e) {
    setTexto(e.target.value)
  }

Debajo del control input se muestra también el valor ingresado por teclado:

      <p>{texto}</p>

Problema 2

Crear una componente que muestre por pantalla la coordenada donde se encuentra la flecha del mouse. En la componente principal disponer un botón para desmontar la componente que muestra la coordenada.

La componente 'CoordenadaFlecha' debe registrar la escucha del evento 'mousemove' y desuscribirse de dicho evento cuando se desmonte la componente.

En principio a medida que movemos la flecha del mouse debe mostrarse la coordenada:

Hook de tipo useEffect

Cuando se presiona el botón se debe desuscribir de la escucha de evento 'mousemove':

Hook de tipo useEffect

Crear con la aplicación create-react-app el proyecto009.

El código fuente de la componente 'CoordenadaFlecha':

import { useState, useEffect } from "react";

function CoordenadaFlecha() {
  const [posicion, setPosicion] = useState({ x: 0, y: 0 })

  useEffect(() => {
    window.addEventListener('mousemove', fijarPosicion)
    return () => { 
      window.removeEventListener('mousemove', fijarPosicion) 
      console.log('se borro el registro de eventos')
    }
  }, [])

  function fijarPosicion(e) {
    setPosicion({ x: e.clientX, y: e.clientY })
  }

  return (
    <div>
      <p>{posicion.x} - {posicion.y}</p>
    </div>
  );
}

export default CoordenadaFlecha;

A la función 'useEffect' le pasamos como primer parámetro la función flecha que registra la escucha del evento 'mousemove' y además retorna una función flecha que se ejecutará cuando se borre la componente 'CoordenadaFlecha' desde la función 'App':

  useEffect(() => {
    window.addEventListener('mousemove', fijarPosicion)
    return () => { 
      window.removeEventListener('mousemove', fijarPosicion) 
      console.log('se borro el registro de eventos')
    }
  }, [])

Además le pasamos como segundo parámetro a la función useEffect un vector vacío con el objetivo que se ejecute una única vez.

El código fuente de la componente 'App':

import { useState } from "react";
import CoordenadaFlecha from './CoordenadaFlecha';

function App() {

  const [visible, setVisible] = useState(true)

  function ocultar() {
    setVisible(false)
  }

  return (
    <div>
      {visible ? <CoordenadaFlecha /> : <p>Se oculto la coordenada</p>}
      <button onClick={ocultar}>Ocultar</button>
    </div>
  );
}

export default App

Cuando se presiona el botón ocultar se llama a la función 'ocultar' que cambia la variable de estado visible por el valor falso, esto hace que la componente CoordenadaFlecha no se renderize y muestre el mensaje 'Se oculto la coordenada':

      {visible ? <CoordenadaFlecha /> : <p>Se oculto la coordenada</p>}