Saltar al contenido

Refs: variable, callback y forwarding

Cómo capturar un nodo del DOM en Solid con la variable local ref o el callback ref, por qué el elemento aún no está insertado cuando el ref se asigna y para eso existe onMount, y cómo reenviar un ref a través de un componente con la prop ref y tiparlo.

Las dos formas de capturar

La forma más común es una variable local. El compilador de Solid reconoce ref={identificador} y, en lugar de leer la variable, genera código que le asigna el nodo al crearlo:

TSX
import { onMount } from "solid-js";function Editor() {  let campo!: HTMLInputElement;         // ! = definite assignment: TS confia en que se asignara  onMount(() => campo.focus());          // aqui el nodo ya esta en el DOM  return <input ref={campo} placeholder="escribe" />;}

Se usa let, no const, porque Solid va a asignarla. El ! es la aserción de asignación definitiva de TypeScript: le prometes al compilador que estará asignada antes de usarla. La otra forma es un callback, útil cuando quieres reaccionar en el momento de la creación o registrar el nodo en una estructura (por ejemplo, en una lista):

TSX
<For each={items()}>  {(item) => <li ref={(el) => registrar(item.id, el)}>{item.nombre}</li>}</For>

El callback recibe el elemento como argumento en cuanto se crea. Cada iteración de <For> obtiene su propio nodo, así que es el patrón natural para guardar referencias a elementos dinámicos.

Cuándo existe el DOM

Aquí está el matiz que hay que grabar. Tanto la variable como el callback se resuelven durante la creación del elemento, que ocurre antes de que su padre lo inserte en el documento vivo. En ese instante el nodo existe como objeto, pero el.parentNode puede ser null y cualquier medida de layout —offsetWidth, getBoundingClientRect()— da cero, porque el elemento todavía no forma parte de la página.

La regla operativa es simple: usa el ref para capturar el nodo, y onMount para usarlo cuando ya está adjunto. onMount corre una sola vez, después del render inicial, cuando el árbol que devuelve el componente ya está insertado. Todo lo que dependa de estar en el DOM —enfocar, medir, arrancar un ResizeObserver o IntersectionObserver, inicializar una librería de terceros— va dentro de onMount, no junto a la asignación del ref.

TSX
function Lienzo() {  let canvas!: HTMLCanvasElement;  onMount(() => {    // Ya esta en el DOM: medir y dibujar tienen sentido aqui.    const rect = canvas.getBoundingClientRect();    const ctx = canvas.getContext("2d")!;    ctx.fillRect(0, 0, rect.width, rect.height);  });  return <canvas ref={canvas} />;}

El mismo patrón sirve para observar el nodo: dentro de onMount ya está adjunto, y un onCleanup registrado ahí se dispara al desmontar.

TSX
import { onMount, onCleanup } from "solid-js";function Panel() {  let caja!: HTMLDivElement;  onMount(() => {    const ro = new ResizeObserver(([entrada]) => medir(entrada.contentRect));    ro.observe(caja);    onCleanup(() => ro.disconnect());   // limpieza atada al ciclo de vida  });  return <div ref={caja} />;}

Forwarding: el ref a través de un componente

Un componente no es un elemento del DOM, así que <MiCampo ref={x} /> no captura nada por sí solo: hay que reenviarlo. La clave es que Solid compila ref={variable} del lado del padre a un callback que asigna esa variable. Por tanto, dentro del componente hijo, props.ref siempre llega como una función que puedes pasar directamente al ref de un elemento nativo:

TSX
import type { JSX } from "solid-js";function Campo(props: {  ref?: HTMLInputElement | ((el: HTMLInputElement) => void);  placeholder?: string;}) {  // Reenvio directo: Solid maneja tanto la forma de variable como la de callback.  return <input ref={props.ref} placeholder={props.placeholder} />;}// Uso: el padre captura el input interno como si fuera propio.function Formulario() {  let campo!: HTMLInputElement;  onMount(() => campo.focus());  return <Campo ref={campo} placeholder="correo" />;}

Si el componente necesita su propio ref interno y además reenviar el del padre, compón ambos en un callback. props.ref es invocable (o undefined), así que basta llamarlo:

TSX
function Campo(props: { ref?: (el: HTMLInputElement) => void }) {  let interno!: HTMLInputElement;  return (    <input      ref={(el) => {        interno = el;                 // tu referencia local        (props.ref as ((e: HTMLInputElement) => void) | undefined)?.(el); // reenvio      }}    />  );}