Deep dive into Compound components in react.

Deep dive into Compound components in react.

  • React in General is less opinionated about How you structure your code, what type of styling pattern you choose(e.g. CSS,scss, CSS module, CSS in js), and what type of state management library you choose(e.g redux,react-recoil, flux, jotai, zustand...). the good thing about having less opinion is that React community comes up with amazing patterns that are more declarative, maintainable, abstract, and testable over the years. for example render props, HOC, custom hooks. And one of that pattern is the Compound component .

  • Idea behind the compound component is that two or more components work together to create more abstract, maintainable, and declarative UI components. Well to be more precise Here is a quote from great kent c dodds.

Think of compound components like the <select> and <option> elements in HTML. Apart they don’t do too much, but together they allow you to create a complete experience.

  • Before discussing too much about the pattern let's just look at code and then we will discuss how it can make our life easier.
<Modal show={showModal} onClose={hideModalHandler}>
        <Modal.Header>stateless Compound Component</Modal.Header>
             <p className="contentContainer">
                   Contrary to popular belief, Lorem Ipsum is not simply random text. It
                   has roots in a piece of classical Latin literature from 45 BC, making
                   it over 2000 years old.
             </p>
        <Modal.Footer>
             <div>
                 <button onClick={hideModalHandler}>Cancel</button>
                 <button onClick={hideModalHandler}>Confirm</button>
             </div>
        </Modal.Footer>
</Modal>
 <Modal show={showModal} onClose={hideModalHandler} title="without Compound Component" footer={
          <div>
               <button onClick={hideModalHandler}>Cancel</button>
               <button onClick={hideModalHandler}>Confirm</button>
          </div>
}>
        <p className="contentContainer">
          Contrary to popular belief, Lorem Ipsum is not simply random text. It
          has roots in a piece of classical Latin literature from 45 BC, making
          it over 2000 years old.
        </p>
 </Modal>
  • if You see both of the code snippets, you will realize that the first code snippet just feels natural and Maintainable while in the second snippet we are passing everything in props (except the main body) so if you want to change anything in the header or footer we have to pass more props to the Modal.but that won't be the case for Compound component.

  • Now let's look at how that code actually works.

function Header({ children }) {
  return <h1 className="header">{children}</h1>;
}

function BackDrop({ clicked, show }) {
  return show ? <div onClick={clicked} className="backdrop" /> : null;
}

export function Footer({ children }) {
  return <div className="action">{children}</div>;
}

export function Modal({ show, children, onClose }) {
  return show ? (
    <>
      <BackDrop show={show} clicked={onClose} />
      <div className="modal">{children}</div>
    </>
  ) : null;
}

Modal.Header = Header;
Modal.Footer = Footer;
  • now let's look at Each component one by one.
  • these two-component just accept children as a prop and put them into a container with style.

BackDrop :-

  • this component is responsible for the backdrop behind the Modal.
  • Main Modal Component has only Backdrop and div with children. and it will be visible whenever the show prop is true.

  • but the main magic happens Here.

Modal.Header = Header;
Modal.Footer = Footer;
  • Here Modal is just javascript function and in javascript functions are also made from object.So here we just added two properties Header and Footer.

  • Here is a live example.

  • In compound components, it is not necessary to write <Modal.Header>. The following code is also 100% correct.

    <Modal show={showModal} onClose={hideModalHandler}>
         <ModalHeader>stateless Compound Component</ModalHeader>
              <p className="contentContainer">
                    Contrary to popular belief, Lorem Ipsum is not simply random text. It
                    has roots in a piece of classical Latin literature from 45 BC, making
                    it over 2000 years old.
              </p>
         <ModalFooter>
              <div>
                  <button onClick={hideModalHandler}>Cancel</button>
                  <button onClick={hideModalHandler}>Confirm</button>
              </div>
         </ModalFooter>
    </Modal>
    
  • Most of the UI libraries use the <Menu/> and <MenuItem/> patterns. but personally <Menu.item> establishes a better relationship with <Menu/>.

  • (For Vue and svelte user: If look at the code shown above you will realize that with the help of the compound component you can also implement named slots in React 🤯)

exercise:-

  • create the Card and Menu components using the compound component and post the link into the comment section.

Stateful Compound Component.

  • Components like Modal, Card are considered as a stateless compound component. because compound components do not share any state between them.

  • but you can also create compound components that share states between them. A famous example of that is the Toggle and Accordion.

  • let's create a Toggle component that will show one component when it is in on state and another component when it is in off state.

  • Here is the anatomy of our Toggle component.
    <Toggle onToggle={toggleHandler}>
        <Toggle.On>
          <p>it is in ON state</p>
        </Toggle.On>
        <Toggle.Off>
          <p>it is in OFF state</p>
        </Toggle.Off>
        <Toggle.ToggleButton />
    </Toggle>
  • there are two ways to create a Toggle component.
  1. create a React context that has toggle state and state updater and its children will consume that context.
  2. Another way is that you insert toggle state and state updater as props to each component in children.
  • we are going to implement the 2nd method here. the reason is that passing state and state updater as props will make our UI more cleaner.

  • but how are we going to manipulate children and insert state and state updater? the answer is combination of React.cloneElement and React.Children.map.

  • enough chit-chat. now let's look at the code.

import React, { useEffect, useState } from "react";

function ToggleButton({ toggleState, onClick }) {
  return (
    <button onClick={onClick}>{toggleState ? "On State" : "Off State"}</button>
  );
}

function On({ toggleState, children }) {
  return toggleState ? <>{children}</> : null;
}
function Off({ toggleState, children }) {
  return toggleState ? null : <>{children}</>;
}

export function Toggle({ children, onToggle }) {
  const [on, setOn] = useState(false);

  function toggleStateHandler() {
    setOn((toggle) => !toggle);
  }

  useEffect(() => {
    onToggle(on);
  }, [on, onToggle]);

  return (
    <div>
      {React.Children.map(children, (child) =>
        React.cloneElement(child, {
          toggleState: on,
          onClick: toggleStateHandler
        })
      )}
    </div>
  );
}

Toggle.On = On;
Toggle.Off = Off;
Toggle.ToggleButton = ToggleButton;
  • Here On and Off component accepts toggleState and children as a prop and displays the component whenever it's appropriate.
  • ToggleButton also accepts toggleState and toggleState updater as a prop and you can change the state whenever you click the button.
  • Now let's look at the main Toggle component. Here, we have declared the on state which determines which component will be shown(Toggle.On or Toggle.Off). onToggle function will be called whenever we change the on state with help of the useEffect hook and at last we are mapping our children by adding toggleState and toggleStateChangeHandler as props.

  • Here is a live working example of it.

  • If you want to see the example with context api check out this blog post by kent c dodds.
  • I hope you learned something from this post and if you have any query feel free to drop a comment but for now that's it from me. have a good day 🤟🤟

exercise :-

  • implement the Accordion component using React.cloneElement.
  • implement this Todo example using context api.
  • post your code link in the comment section