Profile picture

⚒️ Detail-oriented software engineer experienced in developing robust, user-centric products.


🎶 Improvising bits and melodies @diegocasmo.


A Simple React Tabs Component

February 06, 2017

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