Compound Components with React Context API
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.
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.
Final Output will look like this
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:
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>
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:
The Audio Player state is shared between all it’s children and hence it can be used in following different ways.
Full Control
Play Control
Play and Pause Control
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 >