Compound Components with React Context API

Sandip Nirmal
5 min readFeb 23, 2019

--

React is a great UI library for building web/native (React Native) applications. At the heart of React application development is Component. Everything is built as a component in react. You can go read through Thinking in React for understanding.

In this article, we will go one level up and try to understand what is Compound Component and how to build one using React (v. 16.3) Context API.

Compound Component

Compound components are components which co-exist. It allows you to create components which share state implicitly.

For Example the<select> and <option> elements in HTML.

HTML Elements<select> and <option> doesn’t do much on if used separately. But when used together they can do magic. This can be achieved by sharing the same state between different components.

There are two major ways in which you can build Compound Components in React. First one is by using React.cloneElement and other is using Context API.

The first way is very well explained by Ryan Florence in the following video.

Compound Component (Using cloneElement) By Ryan Florence

We will try to build the same example using React Context API.

React Context API

As per official React documentation, Context is

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

If you are not aware or haven’t used Context before, then the following video by Wes Bos will help you.

React Context API Explained By Wes Bos

Final Output will look like this

Compound Tab Component

Setting up the Context

We need to set up the Context to be shared using React context API.

import React from "react";const TabsContext = React.createContext({
selectedTabIndex: 0,
selectTab: () => {},
headers: [],
selectedDetails: ''
});
export default TabsContext;

Setting Provider

Now since Context is set, we will use it in Provider. A Provider is a parent in which we render all the children and pass the context values to be shared across all the children.

import React from "react";import TabsContext from "./TabContext";const tabsData = [
{name: "EPL",description: "English Premier League"},
{name: "IPL",description: "Indian Premier League"},
{name: "Serie A",description: "Italian Football League"}
];
export default class Tabs extends React.Component {
state = {
selectedTabIndex: 0
};
selectTab = selectedTabIndex => {
this.setState({selectedTabIndex});
};
render() {
const { selectTab, state: { selectedTabIndex }} = this;
return (
<TabsContext.Provider value={{
selectedTabIndex,
selectTab,
headers: tabsData.map(({ name }) => ({ name })),
selectedDetails: tabsData[selectedTabIndex].description
}}>
{this.props.children}
</TabsContext.Provider>
);
}
}

Consuming Context Values

We have context and Provider set, we will consume this values in child. This way we will be sharing the same state between Provider and Consumers.

import React from "react";import TabsContext from "./TabContext";
import Tab from "./Tab";
const TabHeader = () => {return (
<div className="tabheader">
<TabsContext.Consumer>
{({ headers, selectedTabIndex, selectTab }) => {
return headers.map(({ name }, index) => (
<Tab
name={name}
key={index}
selected={selectedTabIndex === index}
handleClick={() => {
selectTab(index);
}}/>
));
}}
</TabsContext.Consumer>
</div>);
};
export default TabHeader;

Here we are sharing the values of headers , selectedTabIndex and selectTab function from Provider.

Similarly we need to set up TabDetail component to share selectedDetail value.

Now we can use our compound component in its full capacity.

<Tabs>
<TabHeader/>
<TabDetail/>
</Tabs>

This will result in following:

Tabs Component

You can use the child in any order. If you want Details component to be rendered on top then you can just use it following way.

<Tabs>
<TabDetail/>
<TabHeader/>
</Tabs>
Output

Audio Player

I’ve built another example of Compound Component (Audio Player), just like the one described in video (2nd from top) link in this article. Final implementation looks like following:

Audio Player Final Example

The Audio Player state is shared between all it’s children and hence it can be used in following different ways.

Full Control

Audio Player with Full Controls

Play Control

Audio Player with Only Play Control and Progress

Play and Pause Control

Audio Player with Only Play and Pause Control

Play/Pause Control

Audio Player with Play/Pause Control

You can grab the code for both examples here.

Conclusion

Using compound component you can eliminate conditional and repetitive code. Like in above example if I want to use TabDetails first instead of TabHeader then I’ve to write conditional code inside the Tabs component but this is not required here.

Note*: Do not use Context API to create compound component if you are going to use component inside loop (map), like list item shouldn’t be compound component created using context API. This will create provider for each item level. Which can lead to reduced performace.

📝 Read this story later in Journal.

🗞 Wake up every Sunday morning to the week’s most noteworthy Tech stories, opinions, and news waiting in your inbox: Get the noteworthy newsletter >

--

--

Sandip Nirmal

Software Engineer, F5 Networks Innovation Pvt Ltd, India.