Saltar al contenido

Refs, el DOM y el escape de React

¿Cuándo está poblada una ref y quién la limpia?

Al terminar vas a poder explicar

  • Ubicar en la línea de tiempo el instante en que se asigna una ref
  • Explicar por qué en React 19 ref es una prop común y forwardRef sobra
  • Usar una ref de callback y saber cuándo se la llama con null

Ya viste que useRef es una caja estable sin cola de updates. Ahora la otra mitad: cómo esa caja termina apuntando a un nodo real, y en qué instante exacto.

El momento del commit

React asigna las refs durante el commit, en un punto muy preciso: después de aplicar la mutación al host y antes de correr los layout effects.

la línea de tiempo, con las refs ubicadas
1fase rendertu función corre. ref.current todavía es null.2commit · mutaciónse crean y actualizan los nodos del host.3commit · refs      → ← acá se asignan. Primero null a las viejas, después las nuevas.4commit · layoutcorren los useLayoutEffect. La ref YA está poblada.5   🖌 el navegador pinta6efectos pasivoscorren los useEffect. La ref sigue poblada.

Dos consecuencias que se usan todo el tiempo:

  • Durante el render, ref.current es null en el primer render. Leerla ahí no es un bug tuyo: es que el nodo todavía no existe.
  • En useLayoutEffect ya está lista, y todavía no se pintó. Por eso es el lugar correcto para medir y ajustar sin que se vea el salto.

Refs de callback: el control fino

Si en vez de un objeto le pasás una función a ref, React te la llama con el nodo al montar y con null al desmontar.

1<div ref={(nodo) => {2  if (nodo) observador.observe(nodo);3  else observador.disconnect();      // React llama con null al desmontar4}} />5 6// Desde React 19 también podés devolver una función de limpieza,7// igual que en un efecto:8<div ref={(nodo) => {9  observador.observe(nodo);10  return () => observador.disconnect();11}} />

React 19: ref pasa a ser una prop común

Durante años, ref y key fueron props reservadas: React las sacaba del objeto de props antes de que tu componente las viera. Por eso existía forwardRef: era la única forma de que un componente de función recibiera una ref.

1// Antes de React 192const Campo = forwardRef(function Campo(props, ref) {3  return <input ref={ref} {...props} />;4});5 6// Desde React 19: ref llega en props como cualquier otra cosa.7function Campo({ ref, ...props }) {8  return <input ref={ref} {...props} />;9}

forwardRef sigue funcionando pero quedó obsoleto. key sigue siendo especial, porque la usa el reconciler antes de que exista el componente.

useImperativeHandle: exponer menos

A veces no querés entregar el nodo entero. Un componente de campo puede querer exponer sólo focus y limpiar, sin dar acceso a todo el DOM.

1function Campo({ ref, ...props }) {2  const interno = useRef(null);3 4  useImperativeHandle(ref, () => ({5    focus: () => interno.current.focus(),6    limpiar: () => { interno.current.value = ""; },7  }), []);8 9  return <input ref={interno} {...props} />;10}

Es el equivalente a una interfaz pública: el padre ve lo que decidiste exponer y nada más. Se usa poco, y está bien que así sea — cada uso es una puerta imperativa en un modelo declarativo.

Antes de seguir · predecí

Querés medir el alto de un div y ajustar la posición de un tooltip sin que se vea el salto. ¿Dónde va la medición?

Lo que te llevás

React asigna las refs en el commit, entre la mutación y los layout effects. Por eso son null en el render y ya están listas en useLayoutEffect. En React 19 ref es una prop común. Y una ref es el escape de un modelo declarativo: usala cuando React no modela lo que necesitás, no antes.

Antes de marcarla, comprobá

  • ¿Podés explicar ubicar en la línea de tiempo el instante en que se asigna una ref?
  • ¿Podés explicar explicar por qué en React 19 ref es una prop común y forwardRef sobra?
  • ¿Podés explicar usar una ref de callback y saber cuándo se la llama con null?