Skip to the content.

A Simple React <Tabs/> Component

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 :).