DEV Community

Cover image for React compound components in action
Nazif Barassounon
Nazif Barassounon

Posted on • Updated on • Originally published at nazifbara.com

React compound components in action

Welcome to my first ever written article. It will be about showing you the usage of compound components through an example of a Modal Box. I used a CSS-in-JS library called Emotion for styling. For the sake of conciseness, the styling codes will be omitted. The goal is to see what compound components bring to the table. The code is available on my Github repository.

Something familiar

Take a glance at this code snippet:

<select>
  <option value="value1">option a</option>
  <option value="value2">option b</option>
  <option value="value3">option c</option>
  <option value="value4">option d</option>
</select>
Enter fullscreen mode Exit fullscreen mode

When an <option> is clicked, <select> somehow knows about it. This is because a state is implicitly shared between <select> and <option>. Their awareness of this state allows them, when put together, to do a specific task. I find that we get a nice API out of it. Compound components give the ability to do the same with the help of React Context.

Context provides a way of sharing values down to the component tree implicitly. It means you don't have to pass values from component to component using props. In certain cases, the use of props can be cumbersome; you can easily end up with a lot of components doing nothing with those values but passing them down to their children. With Context, you get direct access to the needed data. It makes Context a great candidate for implementing compound components.

They work together

This is the Modal in use:

// src/App.js

function App() {
  return (
    <Modal>
      <ModalOpenButton>Open modal</ModalOpenButton>
      <ModalContent title="Modal title here!" imageSrc="./forest.jpg">
        <p>
          Modal Content there!
        </p>
      </ModalContent>
    </Modal>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Modal component is a Context provider that yields a boolean state which says if the Modal is open or not.

// src/modal.js

const ModalContext = React.createContext();

function Modal(props) {
  const [isOpen, setIsOpen] = React.useState(false);

  return <ModalContext.Provider value={[isOpen, setIsOpen]} {...props} />;
}
Enter fullscreen mode Exit fullscreen mode

ModalOpenButton consumes the state so that when clicked, the button it returns, set isOpen to true.

// src/modal.js

function ModalOpenButton({ children }) {
  const [, setIsOpen] = React.useContext(ModalContext);
  return <button onClick={() => setIsOpen(true)}>{children}</button>;
}
Enter fullscreen mode Exit fullscreen mode

Then we have ModalContent, also consuming the ModalContext, that put contents (its children) in the Modal. It decides to render the Modal Box when isOpen is true otherwise returns null.

// src/modal.js

function ModalContent({ children, title, imageSrc }) {
  const [isOpen, setIsOpen] = React.useContext(ModalContext);

  return isOpen ? (
    <Overlay onClick={() => setIsOpen(false)}>
      <div
        css={{...}}
        onClick={(e) => e.stopPropagation()}
      >
        <div css={{...}}>
          <h2 css={{..}}>
            {title}
          </h2>
          <ModalCloseButton />
        </div>
        <div css={{...}}>{children}</div>
      </div>
    </Overlay>
  ) : null;
}
Enter fullscreen mode Exit fullscreen mode

Once the Modal is open there are two ways of closing it: clicking the Overlay or the ModalCloseButton. Overlay is a styled component and ModalCloseButton another ModalContext consumer. They both set isOpen to false when clicked.

// src/modal.js

const Overlay = styled.div({...});

function ModalCloseButton() {
  const [, setIsOpen] = React.useContext(ModalContext);

  return (
    <button
      onClick={() => setIsOpen(false)}
      css={{...}}
    >
      <img alt="" src={timeSVG} />
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Here is the list of our compound components:

  • Modal
  • ModalContent
  • ModalOpenButton
  • ModalCloseButton

They are all sync around a common state each taking action to bring specific functionality. Put them apart and they will not be as useful. Thanks for reading!

Top comments (0)