CodeForge

React 19 de Cero a Experto / Efectos y ciclo de vida

useRef: la caja que React no observa

Teoría22 min20 XP

useState guarda valores que, al cambiar, repintan la UI. Pero a veces necesitas guardar algo que NO debe repintar nada: el id de un setInterval, el valor anterior de una variable, o una referencia directa a un elemento del DOM para enfocarlo. Para eso está useRef: una caja que persiste entre renders pero que React NO observa —cambiarla no dispara re-render—. Hoy dominas sus dos usos y, sobre todo, cuándo NO confundirla con el estado.

Una caja con memoria, sin alarma

Uso 1: referenciar un elemento del DOM

El uso más visible: obtener el nodo real del DOM para ordenarle algo que React no expresa declarativamente, como enfocar un input. Pasas la ref al atributo ref del elemento:

inputRef.current es el elemento <input> real del DOM. React conecta la ref al nodo cuando el componente se monta (antes, current es null —por eso el ?.—). Enfocar un input es una orden IMPERATIVA al DOM que React no tiene forma declarativa de expresar, así que es un caso legítimo de ref. Otros: medir el tamaño de un elemento, hacer scroll a una posición, o integrar una librería que necesita un nodo del DOM.

Uso 2: un valor mutable que no repinta

El otro uso, menos visible pero clave: guardar un valor que cambia entre renders sin provocar re-render. El caso típico es el id de un temporizador para poder cancelarlo:

ref-mutable.tsx
function Cronometro() {
const [segundos, setSegundos] = useState(0);
const idRef = useRef<number | null>(null); // guarda el id del intervalo, NO repinta

function iniciar() {
  if (idRef.current !== null) return; // ya está corriendo
  idRef.current = window.setInterval(() => setSegundos((s) => s + 1), 1000);
}

function parar() {
  if (idRef.current !== null) {
    clearInterval(idRef.current);
    idRef.current = null;
  }
}

return (
  <>
    <p>{segundos}s</p>
    <button onClick={iniciar}>Iniciar</button>
    <button onClick={parar}>Parar</button>
  </>
);
}

idRef.current guarda el ticket del setInterval entre renders. Si usáramos una variable normal, se perdería en cada render; si usáramos useState, cambiarla repintaría sin necesidad (el id no sale en la UI). useRef es el punto justo: persiste como el estado, pero es invisible para el render como una variable. Cambiar idRef.current nunca dispara un re-render.

useState o useRef

Necesitas guardar el id que devuelve setInterval para poder cancelarlo luego. ¿useState o useRef, y por qué?

Mini-reto

Usa los dos modos de useRef: 1) un input con una ref y un botón "Limpiar y enfocar" que borre el texto (estado) y luego haga ref.current?.focus(); 2) un useRef que cuente cuántas veces ha renderizado el componente (rendersRef.current++ en el cuerpo, y muéstralo) —observa que sube sin causar renders extra—; 3) explica en un comentario por qué ese contador NO puede ser useState (causaría un bucle infinito de renders). Siente la diferencia entre "se ve/repinta" y "tras bambalinas".

Qué sigue

Ya tienes efectos, cleanup y refs. Es hora del caso rey de los efectos en apps reales: CARGAR DATOS de una API. Parece simple, pero esconde una trampa famosa —la "race condition" cuando dos cargas se pisan— y un patrón de tres estados (cargando / error / datos) que vas a repetir mil veces. La próxima lección lo arma bien desde el principio.