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.
1fase render → tu función corre. ref.current todavía es null.2commit · mutación → se crean y actualizan los nodos del host.3commit · refs → ← acá se asignan. Primero null a las viejas, después las nuevas.4commit · layout → corren los useLayoutEffect. La ref YA está poblada.5 🖌 el navegador pinta6efectos pasivos → corren los useEffect. La ref sigue poblada.Dos consecuencias que se usan todo el tiempo:
- Durante el render,
ref.currentesnullen el primer render. Leerla ahí no es un bug tuyo: es que el nodo todavía no existe. - En
useLayoutEffectya 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?