⚛️ React Q75 / 113

What is compound component pattern?

AI-Powered Answer ✓ Answered

The Compound Component Pattern in React is a design pattern where a parent component and its child components implicitly share state and communicate with each other to manage a complex piece of UI. It allows you to create flexible, expressive, and reusable components where the parent component provides the overall structure and logic, while the children dictate the rendering and interaction details.

Understanding the Pattern

At its core, the Compound Component Pattern involves a parent component that acts as a container and orchestrator, along with several child components that work together to form a cohesive unit. These children are typically rendered directly within the parent in JSX, and they often communicate state or behavior without explicit prop drilling, instead relying on implicit mechanisms like React Context or React.cloneElement.

Think of HTML's <select> and <option> tags. The <select> element manages the state (which option is selected), and the <option> elements represent the individual choices. They work together without the <option> needing to explicitly receive a selectedIndex prop from <select>.

Key Benefits

  • Improved Readability and Usage: Makes components intuitive to use, as the relationship between parent and children is clear and self-documenting in JSX.
  • Flexible UI: Allows users of the component to compose the UI in various ways by arranging the child components, without the parent dictating the exact rendering order or structure.
  • Better Separation of Concerns: The parent handles the state and logic, while children focus on rendering and specific interactions.
  • Implicit State Sharing: Often leverages React Context to share state or methods between the parent and its descendants without prop drilling, making the API cleaner for consumers.
  • Encapsulation: Keeps related components and their logic tightly coupled, preventing external misuse or breaking changes to their internal workings.

Implementation Approach

The most common way to implement this pattern in React involves a combination of techniques:

  • React.Children Utilities: The React.Children object provides methods like map, forEach, and toArray to iterate over a component's props.children and potentially modify or extend them.
  • React.cloneElement: Used to inject props (like state or callbacks) into the children components without the children explicitly receiving them from their parent. This is less common with Context API but useful for simple cases.
  • React Context API: The preferred method for sharing state or methods implicitly down the component tree. The parent component provides a context value, and its compound children consume it.

Simple Example: Menu and MenuItem

jsx
import React, { createContext, useContext, useState } from 'react';

// 1. Create a Context
const MenuContext = createContext(null);

// 2. Parent Component: Menu
function Menu({ children }) {
  const [activeItem, setActiveItem] = useState(null);

  const handleItemClick = (id) => {
    setActiveItem(id);
  };

  const contextValue = {
    activeItem,
    handleItemClick,
  };

  return (
    <ul style={{ listStyle: 'none', padding: 0 }}>
      <MenuContext.Provider value={contextValue}>
        {children}
      </MenuContext.Provider>
    </ul>
  );
}

// 3. Child Component: MenuItem
function MenuItem({ id, children }) {
  const { activeItem, handleItemClick } = useContext(MenuContext);
  const isActive = activeItem === id;

  const style = {
    padding: '8px 12px',
    margin: '4px 0',
    cursor: 'pointer',
    backgroundColor: isActive ? '#e0e0e0' : 'transparent',
    border: '1px solid #ccc',
    borderRadius: '4px'
  };

  return (
    <li style={style} onClick={() => handleItemClick(id)}>
      {children}
    </li>
  );
}

// 4. Expose children as properties of the parent
Menu.Item = MenuItem;

// 5. Usage
function App() {
  return (
    <div>
      <h1>My Application Menu</h1>
      <Menu>
        <Menu.Item id="home">Home</Menu.Item>
        <Menu.Item id="about">About Us</Menu.Item>
        <Menu.Item id="contact">Contact</Menu.Item>
        <Menu.Item id="blog">Blog</Menu.Item>
      </Menu>
    </div>
  );
}

export default App;