Compound Components Pattern

The Compound Components pattern allows you to create expressive and flexible component APIs where multiple components work together to accomplish a task.

What are Compound Components?

Compound components are a pattern where components are used together such that they share an implicit state. Think of HTML elements like <select> and <option> - they work together!

Example Implementation

// Toggle compound component
const ToggleContext = React.createContext();

function Toggle({ children }) {
  const [on, setOn] = React.useState(false);
  const toggle = () => setOn(!on);

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

function ToggleOn({ children }) {
  const { on } = React.useContext(ToggleContext);
  return on ? children : null;
}

function ToggleOff({ children }) {
  const { on } = React.useContext(ToggleContext);
  return on ? null : children;
}

function ToggleButton(props) {
  const { on, toggle } = React.useContext(ToggleContext);
  return <button onClick={toggle} {...props} />;
}

// Usage
<Toggle>
  <ToggleOn>The button is on</ToggleOn>
  <ToggleOff>The button is off</ToggleOff>
  <ToggleButton>Toggle</ToggleButton>
</Toggle>

Benefits

  1. Flexible Markup Structure: Users can rearrange components
  2. Reduced Prop Drilling: State is shared via context
  3. Clear Parent-Child Relationship: API is intuitive
  4. Separation of Concerns: Each component has a single responsibility

Real-World Examples

  • Reach UI’s Accordion
  • Material-UI’s Stepper
  • Ant Design’s Form components

Key Takeaways

  • Use React Context to share state between compound components
  • Provide static properties for better developer experience
  • Consider adding validation for required child components
  • Document the relationship between components clearly