Compound components are a powerful pattern for building flexible UI primitives by allowing a parent component and its sub-components to implicitly share state and logic. Instead of a single, monolithic component managing all its internal parts through a myriad of props, the compound pattern enables you to expose individual sub-components that work cooperatively. Think of the native HTML <select> and <option> tags; they function together, with <option> relying on its <select> parent for shared behavior, but you control their exact placement and content. This approach dramatically enhances reusability and expressiveness, making components easier to understand and use.
The core idea is to establish a shared context that child components can access without prop drilling. Typically, this is achieved in React using the Context API, where the 'parent' compound component acts as a Provider for a specific context, and its 'child' components (e.g., Tab, TabPanel within a Tabs component) consume this context using useContext. This allows the sub-components to read or update the shared state (like the activeTab in a Tabs component) and react to changes without direct prop passing. Consumers of your compound component then compose the UI by arranging these sub-components as direct children, giving them granular control over the rendering structure and content.
This pattern is particularly effective for complex UI elements like Accordion, Dropdown, Tabs, or Form components where different parts need to coordinate. It promotes a clearer separation of concerns, improves component readability, and provides a superior developer experience by making the API intuitive and composable. By abstracting away the internal state management into a well-defined parent-child relationship via context, you empower developers to build highly customized UIs while maintaining a robust and maintainable component library.
Key Takeaways
- Compound components allow a parent and its sub-components to implicitly share state and logic.
- They enhance UI flexibility and reusability by exposing individual, cooperative parts.
- React Context API is the primary mechanism for sharing state between compound components.
- Avoids prop drilling and provides granular control over component composition for consumers.
- Ideal for complex UI primitives like Tabs, Accordions, or Forms, improving DX and maintainability.
Code Example
How this code works
This code demonstrates how to build a flexible Tabs component system using React's Context API. Its job is to create a set of tabs where clicking a Tab component can change which tab is "active," without the Tabs parent needing to know the specifics of each Tab or vice-versa. It accomplishes this by centralizing the active state management and making it available to all related components.
The Tabs component uses useState to manage activeTab and setActiveTab, which are then shared with its children via TabContext.Provider. Any child component, like Tab, can then access this shared state using useContext(TabContext). Each Tab component reads the activeTab to conditionally apply an 'active' class and, crucially, its onClick handler calls setActiveTab with its own unique id. A subtle but important detail is that TabContext is initially created with null. However, the Tabs component immediately provides a meaningful value containing the activeTab and setActiveTab from useState. This ensures that any Tab component consuming the context always receives a valid state and setter function, preventing errors that could arise if it tried to deconstruct null.