useRef en React: valores persistentes sin render | Nicolás Garzón
useRef
ref.current
TypeScript
Copiar const timeoutRef = useRef < number | null > ( null ) ;
Referenciar nodos DOM.
Guardar IDs de timers.
Conservar una instancia imperativa.
Mantener información que no participa en la UI.
TypeScript
Copiar function Search ( ) {
const inputRef = useRef < HTMLInputElement> ( null ) ;
return (
< >
< input ref= { inputRef} / >
< button onClick= { ( ) => inputRef. current?. focus ( ) } >
Enfocar
< / button>
< / >
) ;
} Si el valor debe verse en pantalla, usa estado. Una ref puede cambiar sin que el usuario vea el cambio.
Salvo inicialización predecible, evita usar ref.current como dato reactivo dentro del render. React no rastrea esos cambios.
Una ref permite interactuar con APIs imperativas, pero no debería sustituir el flujo declarativo normal.
Guardar datos visuales en refs.
Usar refs para evitar dependencias de effects.
Manipular directamente nodos que React también controla.
Exponer todo el nodo DOM cuando basta una acción limitada.
State representa información visual reactiva. Ref conserva información mutable que React no necesita renderizar.
useRef devuelve un objeto cuya identidad se conserva durante la vida de la instancia:
TypeScript
Copiar const ref = useRef ( initialValue) ; Texto
Copiar render 1 → ref object ─┐
render 2 → same object ├→ ref.current mutable
render 3 → same object ┘Cambiar ref.current no solicita render porque React no utiliza ese valor para calcular automáticamente la interfaz.
TypeScript
Copiar const timeoutIdRef = useRef < number | null > ( null ) ; Conserva información necesaria entre eventos o effects, pero invisible para la UI.
TypeScript
Copiar const inputRef = useRef < HTMLInputElement> ( null ) ;
return < input ref= { inputRef} / > ; React asigna el nodo durante commit y coloca null al desmontar.
Texto
Copiar ¿debe persistir entre renders?
├─ no → variable local
└─ sí
¿debe cambiar la UI?
├─ sí → state
└─ no → ref
Resultado derivado: variable local.
Texto visible editable: state.
ID de interval: ref.
Nodo para focus: ref.
Contador visible: state.
TypeScript
Copiar function SearchField ( ) {
const inputRef = useRef < HTMLInputElement> ( null ) ;
function handleClear ( ) {
if ( ! inputRef. current) return ;
inputRef. current. value = "" ;
inputRef. current. focus ( ) ;
}
return (
< >
< input ref= { inputRef} defaultValue= "React" / >
< button type= "button" onClick= { handleClear} > Limpiar< / button>
< / >
) ;
} Esta manipulación es coherente porque el input es no controlado. En un input controlado, limpia mediante state y usa la ref solo para focus.
TypeScript
Copiar function DelayedMessage ( ) {
const timeoutRef = useRef < number | null > ( null ) ;
function schedule ( ) {
if ( timeoutRef. current !== null ) {
window. clearTimeout ( timeoutRef. current) ;
}
timeoutRef. current = window. setTimeout ( ( ) => {
} , 500 ) ;
}
useEffect ( ( ) => {
return ( ) => {
if ( timeoutRef. current !== null ) {
window. clearTimeout ( timeoutRef. current) ;
}
} ;
} , [ ] ) ;
} La ref permite que handlers y cleanup compartan el identificador actual.
Puedes actualizar una ref después del commit:
TypeScript
Copiar function usePrevious < T > ( value: T ) {
const previousRef = useRef < T > ( ) ;
useEffect ( ( ) => {
previousRef. current = value;
} , [ value] ) ;
return previousRef. current;
} En el render actual devuelve lo guardado por el commit anterior. Documenta claramente esta semántica.
TypeScript
Copiar ref. current += 1 ; Hace que el resultado dependa del número de ejecuciones, que puede variar en Strict Mode o trabajo descartado.
Existe una inicialización predecible aceptable cuando el resultado es idéntico y solo se realiza una vez:
TypeScript
Copiar const playerRef = useRef < VideoPlayer | null > ( null ) ;
if ( playerRef. current === null ) {
playerRef. current = new VideoPlayer ( ) ;
} Debe ser pura respecto al render y no depender de efectos observables. Para recursos con lifecycle externo, un effect puede ser más apropiado.
Guardar “el último valor” en una ref puede ser válido para callbacks imperativos:
TypeScript
Copiar const latestCallbackRef = useRef ( onMessage) ;
latestCallbackRef. current = onMessage; Pero no lo uses automáticamente para esconder dependencias de un effect. Si la sincronización debe reaccionar a un valor, ese valor es una dependencia. En React moderno, useEffectEvent expresa mejor ciertos casos de lógica no reactiva.
Refs son apropiadas para:
focus().
scrollIntoView().
Medición.
Selección de texto.
Integración con mapas, charts o reproductores.
Evita eliminar, reordenar o crear manualmente nodos que React administra. La estructura visible debe derivarse de state.
Una función ref recibe el nodo cuando se asigna y null cuando se retira:
TypeScript
Copiar function MeasureItem ( ) {
const handleRef = useCallback ( ( node: HTMLDivElement | null ) => {
if ( node) {
console . log ( node. getBoundingClientRect ( ) ) ;
}
} , [ ] ) ;
return < div ref= { handleRef} > Contenido< / div> ;
} Strict Mode puede repetir setup y cleanup de ref callbacks en desarrollo; deben ser reversibles.
Un componente puede reenviar una ref al nodo o exponer un handle pequeño con useImperativeHandle. No expongas su DOM interno por defecto: aumenta acoplamiento y dificulta cambiar implementación.
No existe nodo DOM durante server rendering. ref.current es null hasta el commit cliente. No bases el HTML inicial en mediciones ni browser APIs leídas desde refs.
Cuando ref.current es null:
Comprueba que el nodo está renderizado.
Espera al commit; no lo leas durante el primer render.
Revisa render condicional y keys.
Confirma que la ref llega al elemento correcto.
Revisa cleanup o desmontaje.
Cuando la UI no cambia tras modificar una ref, es el comportamiento esperado: usa state si debe renderizarse.
Una ref es una caja mutable estable que React no observa para render.
State expresa información visual; refs guardan detalles imperativos.
Los nodos aparecen en refs después del commit.
Evita usar refs como state oculto o para engañar dependencias.
La manipulación DOM debe ser limitada y compatible con el ownership de React.
¿Por qué cambiar ref.current no actualiza la pantalla?
¿Cuándo un input puede limpiarse directamente mediante ref?
¿Por qué escribir una ref durante cada render es peligroso?
¿Qué diferencia existe entre object ref y callback ref?
Ver respuestas
Porque React no encola render al cambiar una ref.
Cuando es no controlado y el DOM posee su valor.
Porque render puede repetirse o descartarse y deja de ser puro.
La object ref almacena current; la callback ejecuta setup y cleanup al recibir el nodo.
useEffect explica cómo refs participan en sincronizaciones que necesitan setup y cleanup después del commit.