Uno de los principales problemas al que nos enfrentamos al desarrollar aplicaciones grandes con React es cómo pasar la información de padres a hijos, sobre todo cuando tenemos componentes con anidaciones muy profundas. Normalmente, esto se soluciona con “contextos” o con “stores” como Redux, Zustand

Pero con React hay otra opción de heredar esas propiedades de una manera más sencilla, usando menos código y creando aplicaciones más eficientes: los Compound Components, un patrón avanzado de desarrollo en React para mejorar la composición de los componentes, así como la herencia de propiedades.

El temido Prop Drilling

Seguramente, en más de una ocasión te has encontrado desarrollando un componente al que, con el tiempo, has tenido que añadir múltiples propiedades para que sus componentes hijos (incluso los más profundamente anidados) puedan funcionar correctamente. Este patrón, conocido como Prop Drilling (en inglés, "perforación de props"), se da cuando las propiedades se pasan de componente en componente, aunque algunos de ellos no las necesiten directamente.

¿Por qué debemos evitarlo? Estos son los principales inconvenientes:

Implementar Compound Components

Para evitar esto podemos usar los Compound Components, en los que estructuramos los componentes en bloques lógicos con función específica, dentro de un único componente y compartiendo un state. Esto reduce y simplifica nuestro código de una manera notable. Para implementarlo seguiremos los siguientes pasos:

  1. Crear un componente padre que se encargue de gestionar el estado y la lógica de los subcomponentes.
  2. Definir los subcomponentes que formarán parte del componente compuesto y trabajarán juntos para construir la interfaz deseada.
  3. Utilizar props para comunicar información y permitir la interacción entre el componente padre y sus hijos, recibiendo cada subcomponente las props que necesita.
  4. Renderizar los subcomponentes desde el componente padre, asegurando su correcta integración y funcionamiento conjunto.

Ejemplo de implementación

A continuación, veamos cómo implementar un ejemplo de Compound Component para un header configurable en React.

Partimos de un componente que recibe muchas props y que se las pasa directamente a sus hijos.

<Header 
  actions={headerData?.actions}
  logoImg={headerData?.logoImg}
  logoSize={headerData?.logoSize}
  dataTestId="headerTest"
  handleClose={headerData?.handleClose}
  handleMenuClick={toggle}
  hasClose={headerData?.hasClose}
  hasLogo={headerData?.hasLogo}
  hasMenu={headerData?.hasMenu}
  information={headerData?.information} />

Vamos a modificarlo creando bloques lógicos dentro del componente:

<Header dataTestId="headerTest">
  <Header.Config
    logoImg={headerData?.logoImg}
    logoSize={headerData?.logoSize}
    handleMenuClick={toggle}
    hasLogo={headerData?.hasLogo}
    hasMenu={headerData?.hasMenu}
  />
  <Header.Information information={headerData?.information} />
  <Header.Actions actions={headerData?.actions} />
  <Header.Close handleClose={headerData?.handleClose} hasClose={headerData?.hasClose} />
</Header>

Ahora vamos al archivo header.tsx, en el que tenemos esos mismos bloques con su funcionalidad y recibiendo solo las props que necesitan:

export const Header = ({ dataTestId, children }) => {
  return (
    <header data-testid={dataTestId}>
      {children}
    </header>
  );
};

Header.Config = function HeaderConfig({
  logoImg = "default",
  logoSize = 50,
  handleMenuClick,
  hasLogo = true,
  hasMenu = true,
}) {
  return (
    <div >
      {hasMenu && (
        <div onClick={handleMenuClick}>
          <BurguerIcon />
        </div>
      )}
      {hasLogo && (
        <div onClick={handleMenuClick}>
          <LogoIcon size={logoSize} logoImg={logoImg} />
        </div>
      )}
    </div>
  );
};

Header.Information = function HeaderInformation({ information }) {
  return (
    <div >
      <HeaderInformation information={information} />
    </div>
  );
};

Header.Actions = function HeaderActions({ actions }) {
  return (
    <div>
      <HeaderActionGroup items={actions?.items} renderer={actions?.renderer} />
    </div>
  );
};

Header.Close = function HeaderClose({ hasClose = false, handleClose }) {
  return (
    hasClose && (
        <div onClick={handleClose}>
          <CloseIcon />
        </div>
    )
  );
};

¿Cuáles son las ventajas de los Compound Components?

  1. Mejor composición
    Permiten estructurar los componentes de manera modular y lógica.
  2. Reutilizabilidad
    Los subcomponentes son altamente reutilizables y pueden adaptarse a diferentes casos de uso.
  3. Responsabilidad única
    Cada subcomponente se encarga de una tarea específica, lo que mejora la mantenibilidad.

Conclusión

Los Compound Components son un patrón de composición de componentes muy útil para construir interfaces complejas de una manera más visual e intuitiva. Nos ayuda a evitar el Prop Drilling, ya que permite organizar mejor la comunicación entre componentes sin necesidad de pasar propiedades innecesarias a través de múltiples niveles. Al utilizar este enfoque, podemos escribir código más limpio, modular y mantenible, facilitando la escalabilidad de las aplicaciones en React.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete