Menu

Hook useRef en React: refs al DOM y valores persistentes

useRef le da a un componente una caja, { current }, que conserva su contenido entre renderizados sin provocar un renderizado cuando la cambias. Úsalo para acceder a un nodo del DOM (foco, scroll, medidas) o para guardar el id de un temporizador o cualquier valor que la pantalla no muestra.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

useRef devuelve un objeto, { current: initialValue }, que React conserva durante toda la vida del componente. Cambiar ref.current no vuelve a renderizar nada. Eso hace que las refs sirvan para dos trabajos: guardar un nodo del DOM para llamar a métodos como focus() sobre él, y recordar entre renderizados un valor que la pantalla no necesita mostrar.

Pasar el objeto ref en ref={inputRef} le dice a React que ponga el nodo DOM del <input> en inputRef.current. Cambia focus() por select(), escribe algo y vuelve a hacer clic: el texto queda seleccionado.

La sintaxis

const myRef = useRef(initialValue);

myRef.current;          // read
myRef.current = value;  // write, no re-render
  • initialValue solo se usa en el primer renderizado. Para refs del DOM es null.
  • React devuelve el mismo objeto en cada renderizado. Solo cambia current.
  • Leer y escribir current es JavaScript normal. React no lo sigue.

Un valor que no provoca renderizados

El estado es para lo que muestra la pantalla. Una ref es para lo que el componente necesita recordar pero no mostrar. Este bloque guarda un contador en una ref y otro en el estado, y registra ambos:

Haz clic en Ref + 1 varias veces. La consola muestra ref.current subiendo, pero la página sigue diciendo 0 y no se registra ningún renderizado. Ahora haz clic en State + 1: el componente renderiza, y el contador de la ref en pantalla salta al número que la ref ya tenía. La ref conservó su valor todo el tiempo; React simplemente no tenía motivo para dibujarlo.

Refs vs estado

useStateuseRef
Devuelve[value, setValue]{ current: value }
Al cambiarloVuelve a renderizar el componenteNo pasa nada visible
Cuándo se puede leer el nuevo valorEn el siguiente renderizadoDe inmediato
Se cambia conEl setterAsignación, ref.current = x
Úsalo paraTodo lo que muestra la pantallaIds de temporizadores, nodos del DOM, valores anteriores, banderas

Si tienes dudas, pregúntate si el JSX lee el valor. Si lo lee, va en el estado.

Guardar el id de un temporizador

setInterval devuelve un id que necesitas después para detener el temporizador. Encaja bien en una ref: el manejador de Stop lo necesita, pero la pantalla nunca lo muestra, y guardarlo en una variable normal haría que se perdiera en el siguiente renderizado.

Reemplaza useRef(null) por let intervalRef = { current: null }; y prueba Start, espera dos segundos y luego Stop: el temporizador sigue. Cada tic renderiza el componente, lo que crea un objeto nuevo, así que Stop no limpia nada.

Recordar el valor anterior

Un efecto se ejecuta después del renderizado, así que puede comparar el valor nuevo con el guardado en una ref, y luego guardar el nuevo para la próxima vez:

Haz clic en green y luego en blue: la consola registra changed from red to green y changed from green to blue. Hacer clic en el color que ya está seleccionado no registra nada, porque el efecto solo se ejecuta cuando color cambia.

Trabajar con el DOM

Las refs son la forma de llamar a APIs del navegador para las que React no tiene una prop: focus(), select(), scrollIntoView(), play() en un video, o leer un tamaño con getBoundingClientRect().

const lastItemRef = useRef(null);

function scrollToLast() {
    lastItemRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

<li ref={lastItemRef}>Last item</li>

ref.current es null durante el primer renderizado, porque React todavía no creó el nodo del DOM, y lo vuelve a ser después de quitar el elemento. Léelo en manejadores de eventos y efectos, donde el nodo existe. Para medir un elemento y posicionar algo antes de que el navegador pinte, lee la ref en useLayoutEffect.

No leas ni escribas ref.current durante el renderizado

React espera que el cuerpo del componente sea un cálculo puro: mismas props y estado, mismo JSX. Una ref cambia fuera de ese modelo, así que un renderizado que la lee o la escribe puede mostrar resultados distintos para las mismas entradas, y React puede llamar al cuerpo más a menudo de lo que esperas.

function Bad() {
    const count = useRef(0);
    count.current++;              // writing during render
    return <p>{count.current}</p>; // reading during render
}

Lee y escribe refs en manejadores de eventos y efectos. La única excepción es la inicialización diferida, donde llenas la ref una vez en el primer renderizado:

const playerRef = useRef(null);
if (playerRef.current === null) {
    playerRef.current = new VideoPlayer(); // runs once
}

(La demo del contador con ref de arriba lee clicks.current en el JSX a propósito, para mostrar que la pantalla no la sigue. No copies eso en código real.)

Ref callbacks

En lugar de un objeto ref, puedes pasar una función a ref. React la llama con el nodo del DOM cuando se agrega el elemento. En React 19 la función puede devolver una limpieza, que React llama cuando se quita el elemento. Esto es útil en una lista, donde no puedes llamar a useRef una vez por elemento:

Al cargar, la consola registra una línea attached por elemento. Haz clic en Remove last y solo aparece detached Cheese. trackNode está definida fuera del componente, así que es la misma función en cada renderizado y React deja en paz los demás elementos. Escríbela en línea como ref={(node) => { ... }} y cada clic desconecta y vuelve a conectar todos los elementos, porque cada renderizado pasa una función nueva.

Pasar una ref a tu propio componente

En React 19, ref es una prop normal para los componentes de función. El padre crea la ref, y el hijo la pone en el elemento del DOM que quiere exponer:

Antes de React 19 esto necesitaba forwardRef, que todavía funciona. La página de forwardRef muestra ambas versiones y cómo exponer solo algunos métodos con useImperativeHandle.

Errores comunes

Mostrar el valor de una ref en el JSX. La pantalla no lo sigue. Si el usuario debe ver el valor, usa estado.

Usar ref.current como dependencia de un efecto. [ref.current] lee el valor durante el renderizado y React no sabe cuándo cambia, así que el efecto no se vuelve a ejecutar cuando cambia. Usa una ref callback para reaccionar a que se conecte un nodo.

Leer una ref del DOM durante el primer renderizado. Es null hasta que React crea el nodo. Léela en un efecto o en un manejador.

Crear un objeto nuevo en lugar de usar el hook. const ref = { current: null } en el cuerpo del componente es un objeto nuevo en cada renderizado. Solo useRef te da el mismo cada vez.

Preguntas frecuentes

¿Qué devuelve useRef?

Un objeto simple con una propiedad, current, con el valor inicial que pasas. React devuelve el mismo objeto en cada renderizado, así que lo que pongas en current sigue ahí la próxima vez.

¿Cuál es la diferencia entre useRef y useState?

Cambiar el estado vuelve a renderizar el componente; cambiar ref.current no. Usa estado para los valores que muestra la pantalla, y una ref para los valores que solo necesitan tus manejadores de eventos y efectos, como el id de un temporizador o un nodo del DOM.

¿Por qué ref.current es null?

React asigna el nodo del DOM a ref.current después de crear el nodo, durante el commit. Durante el primer renderizado todavía es null, y vuelve a ser null después de quitar el elemento. Léelo en un manejador de eventos o en un efecto.

¿Cambiar ref.current provoca un nuevo renderizado?

No. React no sabe que lo cambiaste. El nuevo valor solo aparece en pantalla si otra cosa, como una actualización de estado, vuelve a renderizar el componente.

¿Cómo paso una ref a un componente hijo?

En React 19 un componente de función recibe ref como una prop normal, así que pasas <MyInput ref={inputRef} /> y el hijo la pone en su <input>. Las versiones anteriores necesitaban forwardRef.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR