Compound Components in React: Build Reusable & Flexible UI

Compound Components in React: Build Reusable & Flexible UI

What are compound components?

Compound components are a technique for creating highly customizable components in React. They involve breaking down a component into smaller, reusable subcomponents that work together to create more complex, composite components. This approach makes them more flexible, customizable, and easier to maintain.

Example

First let’s take a look at a simple example of a Modal component:

export function Modal({ title, onClose, text, footerText }) {
  return (
    <div className="modal">
      <div className="modal-header">
        <h2>{title}</h2>
        <button onClick={onClose}>X</button>
      </div>
      <div className="modal-body">
        <p>{text}</p>
      </div>
      <div className="modal-footer">
        <p>{footerText}</p>
      </div>
    </div>
  );
}

This component is perfectly fine. It works. It does what it’s supposed to do. We can use it like this:

<Modal
  title="Modal title"
  text="Modal text"
  footerText="Modal footer text"
  onClose={() => console.log("Modal closed")}
/>

But what if at some point we would like change the behaviour or the appearance of the modal? What if we don’t want to show the close button? Or maybe we want to add a custom button? Or change the color of the header? Maybe it should look different in certain parts of the application. And so on.
You get the idea. We want to be able to change the appearance of the modal, but we don’t want to have to pass a ton of props to the component. Standard React components are not very flexible. They are designed to be used in a certain way, and they are not very customizable. Which is perfectly fine, because most of the time you don’t need a lot of flexibility. But sometimes you do.
And that’s where compound components come in.

Compound components to the rescue!

Let’s take a look at the same example, but this time we will use compound components:

const ModalContext = createContext();

export function Modal({ children, onClose }) {
  return (
    <ModalContext.Provider value={{ onClose }}>
      <div className="modal">{children}</div>
    </ModalContext.Provider>
  );
}

Modal.Header = function Header({ title }) {
  const { onClose } = useContext(ModalContext);
  return (
    <div className="modal-header">
      <h2>{title}</h2>
      <button onClick={onClose}>X</button>
    </div>
  );
};

Modal.Body = function Body({ children }) {
  return <div className="modal-body">{children}</div>;
};

Modal.Footer = function Footer({ children }) {
  return (
    <div className="modal-footer">
      <p>{children}</p>
Modal.Footer = function Footer({ children }) {
  return (
    <div className="modal-footer">
      {children}
    </div>
  );
};
```javascript jsx
<Modal onClose={() => console.log("Modal closed")}>
  <Modal.Header title="Modal title" />
  <Modal.Body>Modal text</Modal.Body>
  <Modal.Footer>Modal footer text</Modal.Footer>
</Modal>

or like this:

<Modal onClose={() => console.log("Modal closed")}>
  {/* We just removed the header component from the modal */}
  <Modal.Body>Modal text</Modal.Body>
  <Modal.Footer>
    <button onClick={() => console.log("Custom button clicked")}>
      Custom button
    </button>
  </Modal.Footer>
</Modal>

As you can see, we can now customize the modal component in a much more flexible way. We can add, remove, or change the appearance of the subcomponents without having to pass a ton of props to the component. And we can do all of this without having to change the modal component itself.

Note: The example above is a simplified version of the compound components pattern. In a real world application you would need to take care of things like accessibility, error handling, click outside events (to close the modal) etc. But the basic idea is the same.

Conclusion

Compound components can lead to a more modular, flexible, and maintainable codebase. They are a great way to create highly customizable components in React. They are not a silver bullet though. But they can be a great tool in your toolbox. Next time you face a problem where you need a lot of props to customize a component, consider using compound components instead.