useRef: la caja que no avisa
¿Por qué mutar una ref no vuelve a renderizar?
Al terminar vas a poder explicar
- Explicar por qué mutar .current no agenda trabajo
- Elegir entre estado y ref con un criterio, no por costumbre
- Detectar el caso en que una ref hace que la UI muestre datos viejos
A useRef se lo suele presentar como «el hook para acceder al DOM». Eso es un caso de uso, no una definición. La definición es más simple y más útil:
useRef devuelve el mismo objeto en todos los renders, y ese objeto no tiene cola de updates.
Todo lo demás sale de ahí.
Un hook sin cola
Volvé a mirar la anatomía de un hook: memoizedState, queue, next. En un useState, la queue es lo que conecta el setter con el planificador de React.
En un useRef, memoizedState es el objeto { current } y queue es null. No hay setter. No hay nada que llame a scheduleUpdateOnFiber. Mutar .current es asignar una propiedad de un objeto común — React ni se entera.
Dos mutaciones de la ref, cero renders
AGENDADOpaso 1 / 121Sin hooks en este paso.
Árbol de fibers
- <HostRoot>
Salida
Nada en el host todavía. React no commiteó ningún cambio.
createRoot().render()
Montaje inicial. No hay árbol previo, así que todo se va a crear desde cero.
VER EL CÓDIGO QUE SE ESTÁ TRAZANDO
1function ConRef() {2 const clicksRef = useRef(0);3 const [mostrado, setMostrado] = useState(0);4 5 return (6 <div>7 <button onClick={() => { clicksRef.current += 1; }}>8 sumar a la ref (no renderiza)9 </button>10 <button onClick={() => setMostrado(clicksRef.current)}>11 traer la ref al estado12 </button>13 <p>mostrado: {mostrado}</p>14 </div>15 );16}Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.
Los dos primeros clicks no producen ni un solo paso de fase render. La traza salta directo del evento a la nota. El tercer click sí renderiza — pero no porque cambió la ref, sino porque su valor se metió en un useState.
Cuándo es la respuesta correcta
- Identificadores de temporizadores e intervalos. Guardás el id para poder cancelarlo. Nadie lo muestra en pantalla.
- Nodos del DOM. Los pasás como
refy React los completa. - Valores previos. Guardar el valor del render anterior para compararlo.
- Banderas de «esto ya lo hice». Evitar que un efecto se ejecute dos veces por lógica propia.
La trampa: la UI se queda vieja
El error clásico es guardar en una ref algo que sí se muestra. El valor cambia, la pantalla no, y no hay ningún error que te avise.
1function Contador() {2 const n = useRef(0);3 4 return (5 <button onClick={() => { n.current += 1; }}>6 {n.current} {/* ✗ nunca se actualiza en pantalla */}7 </button>8 );9}El n.current sube perfectamente. Lo que no pasa es el render que lo mostraría. Y cuando algún otro estado provoque un render por su cuenta, el número va a saltar de golpe al valor acumulado — lo que hace el bug todavía más confuso.
El criterio es de una sola pregunta: ¿este valor se ve? Si se ve, es estado. Si no se ve, puede ser una ref.
Antes de seguir · predecí
Querés mostrar cuántas veces se renderizó un componente, en pantalla. ¿Ref o estado?
Lo que te llevás
useRef es una caja estable sin cola de updates. Mutarla no agenda trabajo — esa es exactamente su gracia y también su única trampa. Si el valor se muestra, no es una ref.
Antes de marcarla, comprobá
- ¿Podés explicar explicar por qué mutar .current no agenda trabajo?
- ¿Podés explicar elegir entre estado y ref con un criterio, no por costumbre?
- ¿Podés explicar detectar el caso en que una ref hace que la UI muestre datos viejos?