useRef: Referencias al DOM

Referencias al DOM y manipulación de datos sin re-renders


Hemos mencionado que en el ecosistema de React no se suele hacer referencia al DOM, sino que trabajamos con el JSX de los componentes funcionales y simplemente React dispara re-renders cuando los datos del componente cambia, actualizando los datos y su interfaz visual asociada, que a su vez modifica el DOM de forma automática, sin que el desarrollador tenga que hacerlo manualmente.

En algunas ocasiones tendremos excepciones donde necesitaremos acceder a elementos del DOM manualmente, o simplemente acceder a datos que no queremos que causen re-renders en nuestros componentes al cambiar.

Algunos de esos casos podrían ser:

  • ✨ Poner el foco en un <input>, ejecutando el método focus() de ese elemento del DOM.
  • ✨ Desplazarse a un elemento del DOM, ejecutando el método scrollIntoView() del elemento.
  • ✨ Manipular un <canvas> para obtener el contexto y trabajar con él.
  • ✨ Almacenar un temporizador setInterval() sion que cause re-renders.

Para ello, lo ideal es utilizar el hook useRef.

¿Qué es el hook useRef?

El hook useRef nos permite crear referencias a un elemento del DOM o a un dato que va a cambiar pero que queremos que no dispare re-renders en nuestro componente. Esto es muy habitual cuando necesitamos controlar manualmente un elemento del DOM o cuando queremos que algo, aunque cambie, no dispare re-renders innecesarios.

Veamos un ejemplo donde creamos un componente que envuelve un dialog modal nativo de HTML. Este componente necesita acceder al atributo open para saber si está mostrándose u oculto, y llamar a su método showModal() para mostrarlo de forma modal, por lo que utilizaremos useRef para ello.

A grandes rasgos, las partes importantes son las siguientes:

  • 1️⃣ Primero, creamos dialogRef, la referencia que apuntará al elemento del DOM. De momento es null.
  • 2️⃣ Observa que, en el JSX, usamos el atributo ref para indicar que ese elemento será la referencia.
  • 3️⃣ Luego, en el interior del useEffect, que depende de isOpen, abrimos o cerramos el modal.
import { useRef, useEffect } from "react";

export function ModalDialog({ isOpen, onClose, children }) {
  const dialogRef = useRef(null);

  useEffect(() => {

    if (isOpen) {
      dialogRef.current?.showModal();
    } else {
      dialogRef.current?.close();
    }

  }, [isOpen]);

  return (
    <dialog ref={dialogRef} onClose={onClose}>
      {children}
      <button onClick={onClose}>Cerrar</button>
    </dialog>
  );
}
  /* ... */
  const [isModalOpen, setModalOpen] = useState(true);

  return (
    <div>
      <button onClick={() => setModalOpen(!isModalOpen)}>Toggle</button>
      <ModalDialog isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
        <p>¡Esto es un ejemplo de dialog nativo envuelto con React!</p>
      </ModalDialog>
    </div>
  );
  /* ... */

La primera vez que se ejecute este componente, dialogRef tendrá el valor null porque aún no se ha creado el <dialog>, pero gracias al useEffect, desde que isOpen cambie, se actualizará con el elemento del DOM que queremos. Luego, en la otra pestaña podemos ver que a la hora de utilizarlo, sólo tenemos que crear un estado para definirlo.

En este caso, aunque isOpen esté cambiando, el componente no se re-renderizará, sino que ejecutará la lógica definida sin causar ni disparar un nuevo renderizado.

Ten en cuenta que para acceder a la referencia hemos accedido a la propiedad .current.

Buenas prácticas de useRef

Al utilizar useRef, como con cualquier otro hook o aspecto de React, hay que ser cuidadoso y no utilizar a la ligera este recurso, para no caer en malas prácticas o hábitos. A continuación tienes una serie de recomendaciones:

  • 🟥 Evita usar useRef para estados que deben causar re-renderizados.
  • 🟩 El hook useRef es ideal para referencias a elementos del DOM.
  • 🟩 Usa useRef para almacenar estados mutables (persistentes entre renders) que no afecten a la UI.
  • 🟩 El hook useRef es ideal para almacenar elementos y acceder a sus métodos directamente.
  • 🟩 Usa useRef + useEffect para manejar el ciclo de vida y asegurar que el componente está montado.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev