Aprende a implementar compound components en React para construir un sistema de tabs modular, flexible y tipado con TypeScript. El componente padre controla el estado, los hijos renderizan el contenido. Un enfoque claro para escalar interfaces sin perder simplicidad.
¿Qué es el patrón compound components en React y cómo se aplica a tabs?
El patrón compound components coordina varios componentes que trabajan juntos. El padre decide qué mostrar y el hijo se enfoca en renderizar. Aquí, el padre es un wrapper llamado Tabs y los hijos son Tab: el primero gestiona el estado local y los segundos muestran label y children.
- El padre controla el activeIndex y cambia al hacer clic.
- Los hijos solo reciben props y renderizan contenido.
- La composición hace el sistema más modular y flexible.
¿Qué props y tipos se definen?
- Tab recibe: label: string y children: ReactNode.
- Tabs recibe: children: ReactNode.
- Se tipa con TypeScript usando interfaces y React.FC.
¿Qué utilidades de React se emplean?
- Estado con useState para el índice activo.
- Normalización de hijos con React.Children.toArray.
- Validación con React.isValidElement.
- Manejo de eventos con onClick.
- Uso de key para cada elemento de lista.
Código del componente hijo Tab:
import React, { ReactNode } from 'react';
interface TabProps {
label: string;
children: ReactNode;
}
export const Tab: React.FC<TabProps> = ({ label, children }) => {
return (
<div>
<em>{label}</em>
<span>{children}</span>
</div>
);
};
¿Cómo se construye el componente tabs con estado y validación de hijos?
El componente Tabs necesita un estado local para saber qué pestaña está activa y una función para actualizarla al hacer clic. Además, convierte los hijos a arreglo y filtra solo elementos válidos de React. Luego, renderiza una lista de labels y un área de contenido.
- Se inicializa
activeIndex en 0.
- Se define
handleTabClick(index: number) para cambiar el estado.
- Se crea
tabElements con Children.toArray(children) y isValidElement.
- Se mapea para pintar un
<li> por cada tab con su label.
- Se muestra el contenido de
tabElements[activeIndex] dentro de tabContent.
Código del componente padre Tabs:
import React, { ReactNode, useState, Children, isValidElement } from 'react';
interface TabsProps {
children: ReactNode;
}
export const Tabs: React.FC<TabsProps> = ({ children }) => {
const [activeIndex, setActiveIndex] = useState(0);
const handleTabClick = (index: number) => {
setActiveIndex(index);
};
const tabElements = Children.toArray(children).filter((child) =>
isValidElement(child)
);
return (
<div>
<ul>
{tabElements.map((child: any, index: number) => (
<li
key={index}
className={index === activeIndex ? 'tab active' : ''}
onClick={() => handleTabClick(index)}
>
{child.props.label}
</li>
))}
</ul>
<div className={'tabContent'}>
{tabElements[activeIndex] as React.ReactNode}
</div>
</div>
);
};
¿Cómo se usa en el componente padre con etiquetas y contenido?
Una vez definidos Tabs y Tab, se reemplaza el contenedor por Tabs y se agregan varios Tab con su label y children. Al hacer clic, cambia el contenido activo sin lógica extra en los hijos.
- El padre define las pestañas.
- Cada hijo incluye su label y su contenido.
- La interacción actualiza el índice activo y renderiza el nodo correspondiente.
Ejemplo de uso:
<Tabs>
<Tab label="Jirafa">🦒</Tab>
<Tab label="Delfín">🐬</Tab>
<Tab label="Lagarto">🦎</Tab>
</Tabs>
¿Te gustaría aportar mejoras o mostrar tu implementación de tabs con compound components? Comparte tus ideas y tu landing de ejemplos en los comentarios.