← React & TypeScript

React con TypeScript

¿Cómo tipas `children` en un componente y para qué sirve la composición en React?

Ver respuesta — intenta responderla en voz alta primero

children se tipa como ReactNode, el tipo más amplio: acepta JSX, strings, números, arrays o null. La composición consiste en armar interfaces metiendo componentes dentro de otros, en vez de crear componentes gigantes llenos de props.

Cuando escribes <Tarjeta>Hola</Tarjeta>, lo que va entre las etiquetas llega al componente como la prop children. Es la base de la composición: el contenedor decide el marco (borde, padding, título) y quien lo usa decide el contenido.

Los puntos clave:

  • ReactNode es el tipo correcto para children: cubre todo lo que React puede renderizar (elementos JSX, strings, números, arrays, null, undefined).
  • No solo children: cualquier prop puede recibir un componente. Por ejemplo, icono: ReactNode te deja pasar <IconoEstrella /> a una tarjeta.
  • Diferencia entre tipos parecidos:
    • ReactNode: cualquier cosa renderizable. Úsalo casi siempre.
    • ReactElement: solo un elemento JSX (<div />, <Boton />). Un string ya no cabe. Úsalo cuando exiges JSX de verdad.
    • string: solo texto plano. Útil si vas a manipular el contenido como texto.
  • La composición evita el "componente Dios" con 20 props: el contenedor recibe children y cada pantalla lo llena distinto.
import type { ReactNode, ReactElement } from "react";

// Componente contenedor: pone el marco, no decide el contenido
type TarjetaProps = {
  titulo: string;      // solo texto: lo usamos tal cual
  icono?: ReactNode;   // un componente pasado por props
  children: ReactNode; // cualquier cosa renderizable
};

function Tarjeta({ titulo, icono, children }: TarjetaProps) {
  return (
    <section className="tarjeta">
      <header>{icono}<h2>{titulo}</h2></header>
      <div className="tarjeta-cuerpo">{children}</div>
    </section>
  );
}

// ReactElement: aquí exigimos JSX, un string no compila
function Modal({ contenido }: { contenido: ReactElement }) {
  return <div className="modal">{contenido}</div>;
}

// Uso: el mismo contenedor sirve para contenidos muy distintos
// <Tarjeta titulo="Perfil" icono={<span aria-hidden>*</span>}>
//   <p>Nombre: Ana</p>
// </Tarjeta>
// <Tarjeta titulo="Aviso">Solo un texto simple</Tarjeta>
// <Modal contenido={<p>Confirmar acción</p>} />
// <Modal contenido="hola" />
// Error: Type 'string' is not assignable to type 'ReactElement'

Tipar children como string o como JSX.Element y quedarse corto:

type TarjetaProps = { children: string }; // demasiado restrictivo

// <Tarjeta><p>Hola</p></Tarjeta>
// Error: Type 'Element' is not assignable to type 'string'

Con string no puedes pasar JSX, y con ReactElement no puedes pasar texto suelto ni varios hijos con libertad. La corrección es usar el tipo pensado justo para eso:

import type { ReactNode } from "react";

type TarjetaProps = { children: ReactNode }; // acepta texto, JSX, arrays, null...
// Regla mental: para children, ReactNode es la opción por defecto

"Yo tipo children como ReactNode, porque es el tipo que cubre todo lo renderizable: JSX, strings, números, arrays o null. ReactElement es más estricto, solo acepta elementos JSX, y lo reservo para cuando de verdad exijo un elemento. La composición me sirve para no crear componentes con veinte props: hago contenedores como <Tarjeta> que ponen el marco y reciben el contenido por children, y si necesito huecos específicos, paso componentes por props, como un icono: ReactNode. Así reutilizo el contenedor en toda la app."

Reto rápido

Crea un componente Panel que reciba children y una prop opcional piePagina (también renderizable). El pie solo se muestra si viene.

Ver solución
import type { ReactNode } from "react";

type PanelProps = {
  children: ReactNode;
  piePagina?: ReactNode;
};

function Panel({ children, piePagina }: PanelProps) {
  return (
    <section className="panel">
      <div className="panel-cuerpo">{children}</div>
      {piePagina && <footer className="panel-pie">{piePagina}</footer>}
    </section>
  );
}

// Uso: <Panel piePagina={<small>Actualizado hoy</small>}>...</Panel>