¿La asignación a la prop especial ref de React vuelve a ocurrir si el componente se re-renderiza?
Sí, la asignación a la prop especial ref de React vuelve a ocurrir cuando el componente se re-renderiza, pero con matices importantes:
📌 Cómo funciona ref en el ciclo de render
Render inicial: React asigna el
refal nodo DOM o al componente hijo.Re-render:
Si el mismo elemento con el mismo
refsigue presente, React no lo reasigna porque la referencia no cambió. El objetoref.currentse mantiene igual.Si el elemento cambia (por ejemplo, se reemplaza por otro tipo de nodo o se desmonta/remonta), React actualiza el
ref: primero lo limpia (ref.current = null) y luego lo vuelve a asignar al nuevo nodo.
Unmount: cuando el componente se desmonta, React pone
ref.current = null.
✅ Implicación práctica
Si tu componente se re-renderiza pero el nodo DOM asociado al
refno cambia, elrefno se vuelve a asignar; simplemente sigue apuntando al mismo nodo.Si el nodo cambia (por ejemplo, condicionalmente renderizás otro elemento), entonces React sí actualiza el
ref.
📌 Ejemplo ilustrativo
function Ejemplo() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
console.log("Ref apunta a:", inputRef.current);
});
return (
<div>
<input ref={inputRef} />
</div>
);
}
En cada render, el
refsigue apuntando al mismo<input>.Si reemplazás el
<input>por un<textarea>, React primero limpia elrefy luego lo asigna al nuevo nodo.
👉 En resumen: los ref se actualizan solo si el nodo asociado cambia. En un re-render normal, si el nodo es el mismo, la asignación no se repite.
¿Querés que te arme un diagrama visual del ciclo de vida de un ref mostrando cuándo se asigna, cuándo se mantiene y cuándo se limpia?
Para que un componente padre mantenga un array de nodos DOM provenientes de las ref de sus hijos, lo más práctico es usar callback refs en lugar de useRef individuales. De esa forma, el padre controla directamente qué se guarda en el array cada vez que React asigna o limpia una referencia.
📌 Estrategia con callback refs
En el padre definís un array (o mejor, un
useRefque contiene un array mutable).Pasás una función como
refa cada hijo.Esa función recibe el nodo DOM y lo agrega o lo quita del array.
📌 Ejemplo
import React, { useRef } from "react";
function Hijo({ innerRef }: { innerRef: (el: HTMLDivElement | null) => void }) {
return <div ref={innerRef}>Soy un hijo</div>;
}
export default function Padre() {
const nodosRef = useRef<HTMLDivElement[]>([]);
const registrarNodo = (el: HTMLDivElement | null) => {
if (el) {
// Agregar si no está
if (!nodosRef.current.includes(el)) {
nodosRef.current.push(el);
}
} else {
// Limpiar si se desmonta
nodosRef.current = [];
}
};
return (
<div>
<Hijo innerRef={registrarNodo} />
<Hijo innerRef={registrarNodo} />
<button
onClick={() => {
console.log("Nodos DOM actuales:", nodosRef.current);
}}
>
Mostrar nodos
</button>
</div>
);
}
✅ Puntos clave
El padre mantiene el array mutable en
useRef.Cada hijo recibe un callback ref que se encarga de registrar su nodo.
Cuando el hijo se desmonta, React llama al callback con
null, lo que te permite limpiar.Así el padre siempre tiene acceso a todos los nodos DOM de sus hijos.
Este patrón es muy útil cuando necesitás manipular o medir varios nodos hijos desde el padre (por ejemplo, en un odontograma para calcular posiciones o aplicar estilos).
¿Querés que te arme un diagrama visual del flujo de refs mostrando cómo el padre recibe y acumula los nodos de los hijos en un array?
Perfecto, Gabriel. Para evitar que el padre reinicie todo el array cuando un hijo se desmonta y llama al callback con null, lo que tenés que hacer es remover solo ese nodo específico en lugar de vaciar la lista completa.
📌 Estrategia mejorada
Usar un
useRefen el padre para mantener el array de nodos.En el callback ref:
Si recibe un nodo (
eldistinto denull), lo agrega si no está.Si recibe
null, elimina solo ese nodo del array.
📌 Ejemplo
import React, { useRef } from "react";
function Hijo({ innerRef }: { innerRef: (el: HTMLDivElement | null) => void }) {
return <div ref={innerRef}>Soy un hijo</div>;
}
export default function Padre() {
const nodosRef = useRef<HTMLDivElement[]>([]);
const registrarNodo = (el: HTMLDivElement | null) => {
if (el) {
// Agregar si no está
if (!nodosRef.current.includes(el)) {
nodosRef.current.push(el);
}
} else {
// Eliminar solo el nodo desmontado
nodosRef.current = nodosRef.current.filter(n => n !== el);
}
};
return (
<div>
<Hijo innerRef={registrarNodo} />
<Hijo innerRef={registrarNodo} />
<button
onClick={() => {
console.log("Nodos DOM actuales:", nodosRef.current);
}}
>
Mostrar nodos
</button>
</div>
);
}
✅ Resultado
El padre mantiene un array con todos los nodos DOM de los hijos.
Cuando un hijo se desmonta, se elimina solo ese nodo del array.
El resto de los nodos se conserva intacto.
Este patrón es muy útil para listas dinámicas de hijos, porque el padre siempre tiene un array actualizado de referencias sin reiniciar todo.
¿Querés que te arme un diagrama visual del flujo de registro y limpieza mostrando cómo cada hijo agrega o elimina su nodo en el array del padre?
Si el padre no borra la referencia cuando un hijo se desmonta, lo que queda en el array es un puntero a un nodo DOM que ya no existe en el árbol. Técnicamente:
Durante el desmontaje, React llama al callback ref con
null.Si el padre ignora ese
null, el array conserva la referencia previa.Esa referencia apunta a un objeto DOM que fue eliminado del documento, por lo que
ref.currentya no está conectado al árbol.El nodo puede quedar como un objeto huérfano en memoria hasta que el recolector de basura lo libere, pero desde el punto de vista de tu código es un elemento inválido: no responde a consultas ni está en el DOM.
📌 Consecuencias de no limpiar
Podés terminar con un array lleno de referencias obsoletas.
Si intentás manipular esos nodos, obtendrás errores o efectos inesperados (porque ya no están en el DOM).
Puede implicar fugas de memoria si mantenés referencias a nodos desmontados que nunca se liberan.
✅ Recomendación
Siempre que React te pase null en el callback ref, eliminá ese nodo del array. Así mantenés el array sincronizado con los hijos que realmente existen en el DOM.
¿Querés que te muestre un mini ejemplo con logs donde se ve cómo queda el array si no eliminás el nodo desmontado, y cómo se mantiene limpio si sí lo eliminás?
Muy buena observación. Cuando React llama al callback ref con null, efectivamente no te dice qué nodo se desmontó, solo que ese ref ya no apunta a nada. Por eso, si querés mantener un array de nodos en el padre y limpiarlo correctamente, necesitás una forma de identificar cada hijo además del nodo en sí.
📌 Estrategias para saber qué borrar
Pasar un identificador desde el hijo Cada hijo puede recibir un
idúnico y el padre usa eseidpara saber qué nodo eliminar cuando el callback recibenull.tsxfunction Hijo({ id, innerRef }: { id: string, innerRef: (id: string, el: HTMLDivElement | null) => void }) { return <div ref={el => innerRef(id, el)}>Soy hijo {id}</div>; } export default function Padre() { const nodosRef = useRef<Record<string, HTMLDivElement | null>>({}); const registrarNodo = (id: string, el: HTMLDivElement | null) => { nodosRef.current[id] = el; // si es null, se limpia solo ese id }; return ( <div> <Hijo id="a" innerRef={registrarNodo} /> <Hijo id="b" innerRef={registrarNodo} /> <button onClick={() => console.log(nodosRef.current)}>Mostrar nodos</button> </div> ); }👉 Así el padre mantiene un diccionario
{id → nodo}y sabe exactamente cuál borrar.
Usar
forwardRefyuseImperativeHandleSi los hijos son componentes más complejos, podés exponer su nodo o API interna medianteforwardRef. El padre recibe unrefpor cada hijo y puede gestionarlos en un array.
Mantener refs en paralelo a la lista de hijos Si renderizás una lista con
.map(), podés generar un array de refs en el padre y asignarlos directamente a cada hijo. Así cada posición del array corresponde a un hijo específico.tsxconst refs = useRef<HTMLDivElement[]>([]); items.map((item, i) => <div key={item.id} ref={el => refs.current[i] = el} />);
✅ Conclusión
Si solo usás el callback ref con null, no sabés qué borrar porque React no te da esa información. La solución es asociar cada hijo con un identificador o mantener la estructura paralela en el padre, de modo que puedas limpiar solo la referencia correcta.
¿Querés que te arme un mini esquema visual mostrando cómo el padre usa un diccionario {id → nodo} para mantener sincronizadas las refs de los hijos?
No hay comentarios:
Publicar un comentario