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étodofocus()
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 esnull
. - 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 deisOpen
, 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.