React y Preact: props, children y los límites de la hidratación
Cómo se comunica una plantilla .astro con una isla de React o Preact: el paso de props con sintaxis JSX, el contenido anidado que llega como la prop children, los slots con nombre que se convierten en props con nombre, y la frontera dura de la hidratación —que exige props serializables, prohíbe funciones y entrega los children como HTML ya impreso e inerte—. Por qué Preact es la alternativa ligera con la misma API y cuándo esa diferencia de peso decide.
Pasar props a una isla
Desde una plantilla .astro, una isla de React recibe props con la misma sintaxis que cualquier componente: cadenas entre comillas y cualquier otro valor entre llaves. El componente los consume por su firma de props, exactamente como en una app de React.
---import Ficha from '../components/Ficha.jsx';const usuario = { nombre: 'Ada', activo: true };---<Ficha titulo="Perfil" nivel={3} datos={usuario} client:load />Hasta aquí nada sorprende. La trampa está en lo invisible: cuando esa isla lleva client:load, sus props no se quedan en el servidor. Astro las serializa, las incrusta en el HTML y las vuelve a leer en el navegador para reconstruir el componente. Ese viaje de ida y vuelta es lo que impone la primera y más importante restricción del modelo: las props de una isla hidratada deben poder serializarse.
El contenido anidado llega como children
Todo lo que escribes entre la etiqueta de apertura y la de cierre de la isla se convierte en su prop children, el mismo children que conoces de React. Astro renderiza ese contenido a HTML en el servidor y se lo entrega al componente, que decide dónde colocarlo con {children}.
---import Tarjeta from '../components/Tarjeta.jsx';---<Tarjeta client:visible> <p>Este párrafo aterriza en la prop children de la tarjeta.</p></Tarjeta>// Tarjeta.jsxexport default function Tarjeta({ children }) { return <section class="tarjeta">{children}</section>;}El componente posiciona {children} donde quiera dentro de su marcado, igual que un <slot /> en un .astro. La continuidad es deliberada: el contenido anidado de Astro y el children de React son el mismo concepto visto desde dos lados de la frontera. Pero hay un matiz capital que la siguiente sección desarrolla —ese contenido no es JSX vivo, es HTML ya impreso.
Un par de detalles operativos redondean la idea. Si la isla no coloca su {children}, el contenido anidado simplemente no se muestra, exactamente como un slot por defecto que nadie usa en un .astro. Y como ese contenido lo renderiza Astro en el servidor, puede ser cualquier cosa que Astro sepa producir —Markdown, otros componentes .astro, incluso otra isla—, no solo texto: la isla receptora recibe el resultado ya convertido en HTML, indiferente a su procedencia.
Slots con nombre se vuelven props con nombre
Cuando el componente necesita varios huecos, Astro tiende un puente elegante entre su modelo de slots con nombre y el modelo de props de React: un elemento anidado con slot="nombre" llega a la isla como una prop llamada igual que el slot.
---import Dialogo from '../components/Dialogo.jsx';---<Dialogo client:load> <span slot="titulo">Confirmar</span> <p>Cuerpo del diálogo en el slot por defecto.</p> <div slot="pie">Acciones aquí</div></Dialogo>// Dialogo.jsx recibe titulo y pie como props, mas childrenexport default function Dialogo({ titulo, pie, children }) { return ( <div class="dialogo"> <header>{titulo}</header> <main>{children}</main> <footer>{pie}</footer> </div> );}El span con slot="titulo" no va al children, sino que aterriza en la prop titulo; lo mismo el pie. El contenido sin marcar cae en children, como el slot por defecto. Así, el redactor de la plantilla .astro piensa en slots con nombre, y el autor del componente de React piensa en props con nombre, sin que ninguno tenga que adaptarse al vocabulario del otro. Astro traduce entre los dos modelos en la frontera.
Ese mapeo funciona con toda limpieza para nombres simples. Conviene, eso sí, mantener los nombres de slot sencillos y verificar en el componente cómo llegan, porque la traducción del vocabulario de slots de Astro al de props de JavaScript no siempre es literal cuando entran guiones u otros caracteres. Regla de diagnóstico: ante un hueco que no aparece donde esperabas, sospecha primero del nombre del slot antes que de la lógica del componente.
La frontera de la hidratación
Aquí se concentra lo que separa una isla de un componente de React corriente, y merece precisión. Hay dos límites distintos que suelen confundirse.
El primero es la serialización de props. Como los valores viajan del servidor al navegador convertidos en datos, solo cruzan los que son representables como datos. El serializador de Astro es más generoso que un JSON.stringify —admite Date, Map, Set, URL, BigInt y algún tipo más—, pero no puede transportar comportamiento: una función pasada como prop no tiene representación serializable, así que no llega, y con ella se cae la idea de pasar callbacks a una isla desde la plantilla.
---import Boton from '../components/Boton.jsx';---<!-- ERROR conceptual: la funcion no cruza al cliente --><Boton onClick={() => console.log('hola')} client:load />Una isla hidratada define sus manejadores de eventos dentro del componente de framework, no los recibe desde Astro. La plantilla le pasa datos —qué mostrar, con qué configuración—, pero el comportamiento vive del lado del cliente, escrito en React. Esa división no es una carencia: es la consecuencia directa de que la interactividad ocurre en el navegador, donde las funciones existen, y la plantilla se ejecutó antes, en el servidor, donde ya no.
El segundo límite es la naturaleza inerte de los children. El contenido que pasas a una isla se renderiza en el servidor y llega al cliente como HTML ya cocido, envuelto en un marcador que React adopta pero no vuelve a evaluar. La isla puede situar ese {children} donde quiera, pero no puede regenerarlo cuando cambia su estado: si esperas que un hijo se repinte al pulsar un botón de la isla, descubrirás que permanece fijo, porque nunca fue JSX vivo, solo una foto. Cuando necesitas hijos verdaderamente reactivos, deben nacer dentro del componente de framework, no entrar por la puerta de los slots.