Hook useRef
Mantén un valor mutable que persiste a través de renderizados sin provocar re-renderizados.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
// Variable de instancia mutable
const renderCount = useRef(0);
renderCount.current += 1;
// Referencia a elemento del DOM
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// Almacena el valor anterior
const prevValue = useRef(value);
useEffect(() => { prevValue.current = value; });Cuándo usarlo: Necesitas acceder a un elemento del DOM directamente, almacenar un valor mutable que no debe provocar re-renderizados, o hacer seguimiento de un valor anterior.
Ejemplo funcional
"use client";
import { useEffect, useRef, useState } from "react";
export function Stopwatch() {
const [elapsed, setElapsed] = useState(0);
const [running, setRunning] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (running) {
intervalRef.current = setInterval(() => {
setElapsed((prev) => prev + 10);
}, 10);
}
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [running]);
const reset = () => {
setRunning(false);
setElapsed(0);
};
const minutes = Math.floor(elapsed / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
const ms = Math.floor((elapsed % 1000) / 10);
return (
<div className="space-y-3">
<p className="text-3xl font-mono tabular-nums">
{String(minutes).padStart(2, "0")}:{String(seconds).padStart(2, "0")}.
{String(ms).padStart(2, "0")}
</p>
<div className="flex gap-2">
<button
onClick={() => setRunning((r) => !r)}
className="px-3 py-1 border rounded"
>
{running ? "Detener" : "Iniciar"}
</button>
<button onClick={reset} className="px-3 py-1 border rounded">
Reiniciar
</button>
</div>
</div>
);
}Lo que esto demuestra:
- Usar
useRefpara almacenar el ID del intervalo de manera que pueda ser cancelado más tarde - La ref persiste a través de renderizados sin provocar re-renderizados cuando se actualiza
ReturnType<typeof setInterval>proporciona el tipo correcto para el ID del temporizador- La ref se limpia en la función de limpieza del efecto
Análisis profundo
Cómo funciona
useRefdevuelve un objeto mutable con una única propiedad.current- La referencia del objeto es estable — React devuelve el mismo objeto en cada renderizado
- Actualizar
.currentno provoca un re-renderizado - A diferencia de
useState, no hay una función setter — mutarás.currentdirectamente - El objeto ref se crea una vez al montar y persiste hasta que el componente se desmonta
Parámetros y valores de retorno
| Parámetro | Tipo | Descripción |
|---|---|---|
initialValue | T | Valor inicial asignado a .current |
| Retorno | Tipo | Descripción |
|---|---|---|
ref | { current: T } | Objeto ref mutable |
Variaciones
Enfoca una entrada al montar:
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;Hace seguimiento del valor anterior:
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}Almacena el callback más reciente (evita cierres obsoletos):
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
// Usa callbackRef.current en manejadores de eventos o temporizadoresMide un elemento del DOM:
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (divRef.current) {
const { width, height } = divRef.current.getBoundingClientRect();
setSize({ width, height });
}
}, []);Notas de TypeScript
// Ref de DOM — usa `null` como valor inicial con tipo de elemento
const divRef = useRef<HTMLDivElement>(null);
// divRef.current es HTMLDivElement | null
// Ref mutable — pasa el tipo como genérico
const countRef = useRef<number>(0);
// countRef.current es number
// Distinción: useRef<T>(null) crea RefObject<T> (readonly .current)
// useRef<T | null>(null) crea MutableRefObject<T | null>
// Para refs de DOM, usa el primer patrón; para valores mutables, usa el segundoGotchas
-
Leyendo refs durante el renderizado — Acceder a
ref.currentdurante el renderizado (fuera deuseEffecto manejadores de eventos) puede dar resultados inconsistentes. Solución: Lee refs solo en efectos o manejadores de eventos. -
Esperando re-renderizados en mutación — Actualizar
ref.currentno re-renderiza el componente. Solución: Si la UI debe actualizarse, usauseStateen su lugar. -
Ref nula en el primer renderizado — Una ref de DOM es
nullhasta que React la adjunta después del primer renderizado. Solución: Accede a refs de DOM dentro deuseEffecto después de una verificación nula. -
Confusión entre ref y state — Almacenar datos visibles en la UI en una ref significa que la pantalla nunca se actualiza. Solución: Usa refs solo para valores que no necesitan ser mostrados o que impulsen efectos secundarios.
-
Refs de callback vs refs de objeto — Las refs de objeto no pueden notificarte cuando el elemento cambia (por ejemplo, renderizado condicional). Solución: Usa una ref de callback
ref={(node) => { ... }}cuando necesites reaccionar al adjunto/desprendimiento.
Alternativas
| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
useState | El valor debe provocar un re-renderizado cuando cambia | Necesitas un contenedor mutable silencioso |
| Ref de callback | Necesitas ejecutar código cuando una ref se adjunta o se desprende | Solo necesitas una referencia estable a un elemento |
document.getElementById | Fuera de React (raro) | Dentro de componentes React — usa refs en su lugar |
| Variable de nivel de módulo | El valor se comparte entre todas las instancias de un componente | El valor debe ser por instancia del componente |
¿Por qué refs en lugar de variables de módulo? Las variables de nivel de módulo se comparten entre todas las instancias de un componente. Las refs son por instancia — cada componente montado obtiene su propio .current.
Preguntas frecuentes
¿Cuál es la diferencia entre useRef y useState?
useRefalmacena un valor mutable que persiste a través de renderizados pero no provoca re-renderizados cuando se actualiza.useStatealmacena un valor que provoca un re-renderizado cuando se actualiza a través de su setter.- Usa
useRefpara valores que la UI no muestra; usauseStatepara valores que la UI refleja.
¿Por qué mi ref de DOM es nula cuando intento acceder a ella?
- Una ref de DOM es
nullhasta que React la adjunta después del primer renderizado. - Accede a refs de DOM dentro de
useEffect(que se ejecuta después del montaje) o detrás de una verificación nula. - En el primer renderizado, el JSX aún no se ha comprometido al DOM.
¿Cómo escribo una ref de DOM vs una ref de valor mutable en TypeScript?
// Ref de DOM: usa nula inicial, obtén RefObject<T> (readonly .current)
const inputRef = useRef<HTMLInputElement>(null);
// Ref mutable: incluye nula en la unión genérica
const countRef = useRef<number | null>(null);
// countRef.current es number | null (mutable)Gotcha: ¿Por qué actualizar ref.current no causa que mi componente se re-renderice?
- Actualizar
.currentmuta el objeto en su lugar; React no tiene forma de detectar este cambio. - No hay una función setter -- asignas directamente:
ref.current = newValue. - Si la UI debe reflejar el valor, usa
useStateen su lugar.
¿Cómo almaceno el valor anterior de una prop o state usando useRef?
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}- La ref mantiene el valor del renderizado anterior porque
useEffectse ejecuta después del renderizado.
¿Cuándo debo usar una ref de callback en lugar de una ref de objeto?
- Usa una ref de callback
ref={(node) => { ... }}cuando necesites ejecutar código cuando un elemento se adjunta o se desprende (por ejemplo, renderizado condicional). - Las refs de objeto solo te dan la referencia del elemento pero no te notifican de cambios.
- Las refs de callback son útiles para medir elementos o integrar con bibliotecas que no sean React.
¿Puedo usar useRef para almacenar un ID de temporizador o intervalo?
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
intervalRef.current = setInterval(tick, 1000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);- Sí, este es un patrón común. La ref persiste el ID a través de renderizados para que la limpieza pueda acceder a él.
Gotcha: ¿Es seguro leer ref.current durante el renderizado?
- Leer
ref.currentdurante el renderizado (fuera deuseEffecto manejadores de eventos) puede dar resultados inconsistentes. - React puede renderizar tu componente varias veces (características concurrentes), y el valor de ref podría ser de un pase de renderizado diferente.
- Lee las refs solo en efectos o manejadores de eventos.
¿Por qué usar una ref en lugar de una variable de nivel de módulo?
- Las variables de nivel de módulo se comparten entre todas las instancias de un componente.
- Las refs son por instancia -- cada componente montado obtiene su propio
.current. - Si dos instancias de un componente usan la misma variable de módulo, se sobrescriben entre sí.
¿Cómo enfoco una entrada al montar usando useRef?
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;- La ref es
nullhasta que se monta, así que accede a ella dentro deuseEffectcon encadenamiento opcional.
¿Qué es el patrón de ref de "callback más reciente" y por qué es útil?
- Almacena la versión más reciente de un callback en una ref, actualizándola en cada renderizado.
- Usa
callbackRef.currenten temporizadores o oyentes de eventos para siempre llamar a la versión más reciente. - Esto evita cierres obsoletos sin añadir el callback a los arrays de dependencias.
Relacionado
- useState — cuando necesitas que el valor provoque re-renderizados
- useEffect — accede a refs de DOM dentro de efectos después del montaje
- useCallback — estabiliza callbacks que se almacenan en refs
- useId — genera IDs únicos para elementos del DOM en lugar de usar refs para identificación