A client of mine recently gave me the task to code a feature which included designing tabs similar to those of Bootstrap. The client side of such project was built using React and I initially thought about using the react-tabs library, but later on decided to implement it myself as it was a very simple requirement and I wanted to have complete control of how it worked.
The goal of this blog post is to share and explain the code I created to built such functionality. Thus, giving you an alternative if one day you need to create a similar component without having to include yet another dependency to your project.
The Gist
I wanted to create a very flexible tabs component. The main goal was for it to allow any valid jsx
to be rendered inside a tab. I wanted it to look something like this:
<Tabs>
<Tab iconClassName={"icon-class-0"} linkClassName={"link-class-0"}>
<p>content 0</p>
</Tab>
<Tab iconClassName={"icon-class-1"} linkClassName={"link-class-1"}>
<CustomComponent propA={"foo"} propB={this.handleSomething} />
</Tab>
</Tabs>
As you can see, a tab should be able to render any valid jsx
. This gives the developer the ability to pass custom props
for other functionality that might be needed in the future.
The <Tabs/>
component
The <Tabs/>
component holds the state and knows what tab is currently being rendered. It handles the changes to show/hide other tabs. Finally, it also gives the developer the ability to specify a default tab to render when none is selected.
Note: A link to see all code with tests can be found at the end of this blog post.
export class Tabs extends Component {
constructor(props, context) {
super(props, context);
this.state = {
activeTabIndex: this.props.defaultActiveTabIndex,
};
this.handleTabClick = this.handleTabClick.bind(this);
}
// Toggle currently active tab
handleTabClick(tabIndex) {
this.setState({
activeTabIndex:
tabIndex === this.state.activeTabIndex
? this.props.defaultActiveTabIndex
: tabIndex,
});
}
// Encapsulate <Tabs/> component API as props for <Tab/> children
renderChildrenWithTabsApiAsProps() {
return React.Children.map(this.props.children, (child, index) => {
return React.cloneElement(child, {
onClick: this.handleTabClick,
tabIndex: index,
isActive: index === this.state.activeTabIndex,
});
});
}
// Render current active tab content
renderActiveTabContent() {
const { children } = this.props;
const { activeTabIndex } = this.state;
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children;
}
}
render() {
return (
<div className="tabs">
<ul className="tabs-nav nav navbar-nav navbar-left">
{this.renderChildrenWithTabsApiAsProps()}
</ul>
<div className="tabs-active-content">
{this.renderActiveTabContent()}
</div>
</div>
);
}
}
The <Tab/>
component
The <Tab/>
component is a stateless component. Its goal is to simply render a Bootstrap-like tab, allow the developer to specify some custom styling, and call its parent (the <Tabs/>
component) when an onClick
event occurs.
export const Tab = (props) => {
return (
<li className="tab">
<a
className={`tab-link ${props.linkClassName} ${
props.isActive ? "active" : ""
}`}
onClick={(event) => {
event.preventDefault();
props.onClick(props.tabIndex);
}}
>
<i className={`tab-icon ${props.iconClassName}`} />
</a>
</li>
);
};
Conclusion
With very little code and yet a lot of flexibility we have managed to implement a simple React <Tabs/>
component. A gist with the source code needed to implement both components and their tests can be found here.
Have you ever implemented something similar? Did you do something differently? Leave a comment below a let’s learn from each other :).