Compound components is an advanced pattern so it might be overwhelming to use. This guide aims to help you understand the pattern so that you can use it effectively with confidence and clarity.

Note — In this article, we’ll use the new context API introduced in React v16.3. Other articles implement the pattern using React.cloneElement but that involves cloning each component which increases the memory footprint. It also limits the components to be immediate children of parent. So using context API is much better.

What are compound components?

Compound components is a pattern in which components are used together such that they share an implicit state that let’s them communicate with each other in the background.

Think of compound components like the <select> and <option> elements in HTML. Apart they don’t do too much, but together they allow you to create the complete experience. — Kent C. Dodds

When you click on an option, select knows which option you clicked. Like select and option, the components share the state on their own, you don’t have to explicitly configure them.

Example of compound component

<TabSwitcher> <header> <ul className="tablist"> <li> <Tab id="a"> <button>Tab A</button> </Tab> </li> <li> <Tab id="b"> <button>Tab B</button> </Tab> </li> </ul> </header> <main> <TabPanel whenActive="a"> <div> a panel </div> </TabPanel> <TabPanel whenActive="b"> <div> b panel </div> </TabPanel> </main> </TabSwitcher>

When you click on the buttons inside Tab component the corresponding tab panels’ content is rendered.

Why do I need it when I have render props?

Render props is a great pattern. It is very versatile and easy to understand. However this doesn’t mean that we have to use it everywhere. If used carelessly it can lead to obfuscated code.

But they are! I’ve been working on demos of unreleased React features and all my components are 10 callbacks deep. Super powerful but a PITA to compose more than two or theee. There’s gotta be some way we can make it nicer. Render props : callbacks :: async/await : ??? — Andrew Clark (@acdlite) January 24, 2018

How soon do you think before the weenies start calling nested render props as callback hell — Sunil Pai (@threepointone) January 23, 2018

Having too many nested functions inside the markup lead to difficulty in reading. Remember nothing is a silver bullet, not even render props.

Advantages of compound component pattern

By looking at the example some advantages are pretty clear.

The developer owns the markup. The implementation of Tab Switcher doesn’t need a fixed markup structure. You can do whatever you like, nest a Tab 10 levels deep (I’m not judging) and it will still work.

The developer can rearrange the components in any order. Suppose you want the Tabs to come below the Tab Panels. No changes are required in the component implementation, we just have to rearrange the components in the markup.

The components don’t have to be jammed together explicitly, they can be written independently but they are still able to communicate. In the example Tab and TabPanel components are not connected directly but they are able to communicate via their parent TabSwitcher component.

How it works

The parent component ( TabSwitcher ) has some state.

) has some state. Using the context-api, TabSwitcher shares it’s state and methods with child components.

shares it’s state and methods with child components. The child component Tab uses the shared methods to communicate with TabSwitcher .

uses the shared methods to communicate with . The child component TabPanel uses the shared state to decide if it should render its content.

Implementing the TabSwitcher compound component

To implement a compound component, I usually follow these steps.

List down the components required. Write the boilerplate. Implement the individual components.

List down the components required

For the TabSwitcher we need to have two things. One is to know which tab content to show and second is to switch tab panels when user click.

This means we need to control the rendering of tab panel content and have a click event listener on the tabs, so when tab is clicked the corresponding tab panel content is shown.

To accomplish this we need three components.

TabSwitcher — parent component to hold the state Tab — component which tell its parent if its clicked TabPanel — component which renders when parent tells it to

Write the boilerplate

The compound component pattern has some boilerplate code. This is great because in most cases we can write it without much thinking.

import React, { Component, createContext } from 'react'; const context = createContext({}); const { Provider, Consumer } = context; const Tab = ({ id, children }) => ( <Consumer> {({ changeTab }) => <div onClick={() => changeTab(id)}>{children}</div>} </Consumer> ); const TabPanel = ({ whenActive, children }) => ( <Consumer> {({ activeTabId }) => (activeTabId === whenActive ? children : null)} </Consumer> ); class TabSwitcher extends Component { state = { activeTabId: 'a' }; changeTab = newTabId => { console.log(newTabId); this.setState({ activeTabId: newTabId }); }; render() { return ( <Provider value={{ activeTabId: this.state.activeTabId, changeTab: this.changeTab }} > {this.props.children} </Provider> ); } } export default TabSwitcher; export { Tab, TabPanel };

Here we are making a context. The child components will take data and methods from the context. The data will be the state shared by the parent and the methods will be for communicating changes to the state back to the parent.

Implement the individual components

The Tab component needs to listen to click events and tell parent which tab was clicked. It can be implemented like this —

const Tab = ({ id, children }) => ( <Consumer> {({ changeTab }) => changeTab(id)}>{children}} </Consumer> );

Tab component takes id prop and on click event call changeTab method passing its id. This way the parent gets to know which Tab was clicked.

TabPanel component needs to render its children only when it is the active panel. It can be implemented like this —