React Tabs Tutorial: 3 Ways to Implement
Tags: javascript, react, tutorial, webdev
Published: 2020-07-23 13:21:31

This post was originally published at <a href="https://www.devaradise.com/react-tabs-tutorial">https://www.devaradise.com/react-tabs-tutorial</a>
<!-- wp:paragraph -->
<p>Tab is a common component in web applications that organize content in different views. Tabs allow us to break up complex interfaces into more manageable subsections that a user can quickly switch between.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In a React project, we can implement tabs component in many ways. In this post, I am going to show you how to implement react tabs using 3 different ways, with the <a href="https://www.devaradise.com/lab/react/react-tabs">demo examples</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We will create a manual tabs component using react state hooks, a routed react tabs using react-router-dom, and easy React tabs component using react-tabs library.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>With these 3 different ways, I hope you find the right way to implement tabs component that suits your project.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Before we begin, as always, I assume that you’re already know how to initialize a react project using create-react-app because I won’t explain it anymore.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I also use functional component and Hooks to manage state in the component as it is more simple and cleaner than using class-based component.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>How to Implement React Tabs with State Hooks</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Implementing react tabs using state hooks is suitable if you don't want to use any tabs library. You just want to create a simple tabs component that managed by <code>useState()</code> hook.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here is how to implement it.</p>
<!-- /wp:paragraph -->

```javascript
import React, { useState } from 'react'

export default function WithStateHook() {
 
  const [currentTab, setCurrentTab] = useState('tab1');
  const tabList = [
    {
      name: 'tab1',
      label: 'Tab 1',
      content: (
        <div className="tab-content">
          <h2>Tab content 1</h2>
          <p>Here is your tab content. You can separate this as a component.</p>
          <p>Lorem ipsum dolor sit amet ...</p>
        </div>
      )
    },
    {
      name: 'tab2',
      label: 'Tab 2',
      content: (
        <div className="tab-content">
          <h2>Tab content 2</h2>
          <p>Here is your tab content. You can separate this as a component.</p>
          <p>Lorem ipsum dolor sit amet ...</p>
        </div>
      )
    },
    {
      name: 'tab3',
      label: 'Tab 3',
      content: (
        <div className="tab-content">
          <h2>Tab content 3</h2>
          <p>Here is your tab content. You can separate this as a component.</p>
          <p>Lorem ipsum dolor sit amet ...</p>
        </div>
      )
    }
  ];
  
  return (
    <div className="simple-tabs">

      <h1>With State Hook</h1>
      
      <div className="tabs">
        {
          tabList.map((tab, i) => (
            <button 
              key={i}
              onClick={() => setCurrentTab(tab.name)} 
              className={(tab.name === currentTab) ? 'active' : ''}>
                {tab.label}
            </button>
          ))
        }
      </div>
      
      {
        tabList.map((tab, i) => {
          if(tab.name === currentTab) {
            return <div key={i}>{tab.content}</div>;
          } else {
            return null;
          }
        })
      }
    </div>
  )
}
```

<!-- wp:paragraph -->
<p>With using state hook, the simplest way I found to implement tabs component is by declaring a <code>currentTab</code> state and a <code>tabList</code> variable that stores all tab data in an array.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The logic is simple. You just loop the <code>tabList</code> and show the only tab that has matched <code>name</code> value with <code>currentTab</code> value.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>currentTab</code> value will changes as the user clicks a tab. It handled in <code>onClick</code> event attribute in tab markup.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I use <code>button</code> tag for the tab because when I use <code>a</code> tag without <code>href</code> or with invalid <code>href</code> value (#), it returns an <a aria-label="undefined (opens in a new tab)" href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md" target="_blank" rel="noreferrer noopener">error from eslint-plugin-jsx</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I tried to resolve error with <code>eslint-disable-next-line</code> and it works. But, when i built it, the tabs component doesn't render.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I think, React is encouraging us to follow that rule. So, i just follow them.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Back to the codes, you can change the <code>tabList</code> to become a react prop or state if you want to make it dynamic.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>For instance, if you want to make this tab component reusable and shared in your project, you can make the <code>tabList</code> as a prop. So, you can pass the <code>tabList</code> value from any component in your project.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you want to see the demo you can <a href="https://www.devaradise.com/lab/react/react-tabs/with-state-hooks" target="_blank" aria-label="undefined (opens in a new tab)" rel="noreferrer noopener">click this link</a>. As for the codes, you can clone my <a aria-label="undefined (opens in a new tab)" href="https://github.com/syakirurahman/react-lab" target="_blank" rel="noreferrer noopener">react-lab repository on github</a>. It contains all demo examples for all <a href="https://www.devaradise.com/category/react-tutorials">react tutorials</a> I wrote in this blog.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>How to Implement Routed React Tabs with <code>react-router-dom</code></h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Routed tabs component means that each tab has its own route / URL. When a tab clicked, the route will changes. So, this is the best choice if you care about SEO, or simply just want to directly access a tab via URL.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You can implement a routed tabs component using react-router-dom, a popular router library for React. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Before creating a routed tabs component, make sure you install react-router-dom first in your project.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="bash" class="language-bash">npm install react-router-dom</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>After that, you should import react-router-dom in your component like this.</p>
<!-- /wp:paragraph -->

```javascript
import { Route, Switch, NavLink } from 'react-router-dom'
```

<!-- wp:paragraph -->
<p>You should also make sure that your component or its parent component are wrapped inside <code>BrowserRouter</code> because <code>Route</code> only works inside <code>BrowserRouter</code>.</p>
<!-- /wp:paragraph -->

```javascript
<BrowserRouter>
  <Route exact component={Home} path="/"/>
  <Route component={ReactTabs} path="/react-tabs-component"/>
</BrowserRouter>
```

<!-- wp:paragraph -->
<p>Usually, <code>BrowserRouter</code> added in root component like App.js.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Then, you can implement the codes below.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Simple Implementation</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>For this simple implementation, you can just use repeated <code>NavLink</code> for tab menu, <code>Route</code> for tab content, and <code>Switch</code> to make it work as tabs.</p>
<!-- /wp:paragraph -->

```javascript
import React from 'react'
import { Route, Switch, NavLink } from 'react-router-dom'

export default function RoutedTabs(props) {
  const parentPath = props.match.path;
  
  return (
    <div className="routed-tabs">
      <h1>Routed Tabs</h1>
      <div className="tabs">
        <NavLink
          to={parentPath+'/tab-1'}
          activeClassName="active"
          isActive={(match, location) =>
              [parentPath, parentPath+'/tab-1'].includes(location.pathname)
          }
        >
          Tab 1
        </NavLink>
        <NavLink
          to={parentPath+'/tab-2'}
          activeClassName="active"
          isActive={(match, location) =>
              [parentPath+'/tab-2'].includes(location.pathname)
          }
        >
          Tab 2
        </NavLink>
        <NavLink
          to={parentPath+'/tab-3'}
          activeClassName="active"
          isActive={(match, location) =>
              [parentPath+'/tab-3'].includes(location.pathname)
          }
        >
          Tab 3
        </NavLink>
      </div>
      
      <Switch>
        <Route component={
          () => (<div className="tab-content">
            <h2>Tab content 1</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>)
        } exact path={[parentPath, parentPath+"/tab-1"]}/>

        <Route component={
          () => (<div className="tab-content">
            <h2>Tab content 2</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>)
        } path={[parentPath+"/tab-2"]}/>

        <Route component={
          () => (<div className="tab-content">
            <h2>Tab content 3</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>)
        } path={[parentPath+"/tab-3"]}/>

      </Switch>
    </div>
  )
}
```

<!-- wp:paragraph -->
<p>As you can see, the codes above don't have much logic as it's for static tabs. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you want to make a dynamic tabs component like when we implement tabs using state hook, you can see the codes below.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Dynamic Implementation</h3>
<!-- /wp:heading -->

```javascript
import React from 'react'
import { Route, Switch, NavLink } from 'react-router-dom'

export default function RoutedTabsDynamic(props) {
  const parentPath = props.match.path;

  const tabsData = [
    {
      label: "Tab 1",
      path: parentPath+"/tab-1",
      content: (<div className="tab-content">
        <h2>Tab content 1</h2>
        <p>Here is your tab content. You can separate this as a component.</p>
        <p>Lorem ipsum dolor sit amet ...</p>
      </div>),
      defaultTab: true
    },
    {
      label: "Tab 2",
      path: parentPath+"/tab-2",
      content: (<div className="tab-content">
        <h2>Tab content 2</h2>
        <p>Here is your tab content. You can separate this as a component.</p>
        <p>Lorem ipsum dolor sit amet ...</p>
      </div>),
    },
    {
      label: "Tab 3",
      path: parentPath+"/tab-3",
      content: (<div className="tab-content">
        <h2>Tab content 3</h2>
        <p>Here is your tab content. You can separate this as a component.</p>
        <p>Lorem ipsum dolor sit amet ...</p>
      </div>),
    }
  ]
  
  
  return (
    <div className="routed-tabs">
      <h1>Routed Tabs</h1>
      <div className="tabs">
        
        {tabsData.map((data, i) => {
          return (
            <NavLink
              key={i}
              to={data.path}
              activeClassName="active"
              isActive={(match, location) =>
                data.defaultTab
                  ? [parentPath, data.path].includes(location.pathname)
                  : [data.path].includes(location.pathname)
              }
            >
              {data.label}
            </NavLink>
          );
        })}

      </div>
      
      <Switch>
        {tabsData.map((data, i) => {
          return (
            <Route 
              key={i}
              component={() => data.content} 
              exact 
              path={
                data.defaultTab
                  ? [parentPath, data.path]
                  : [data.path]
              }
            />
          );
        })}
      </Switch>
    </div>
  )
}
```

<!-- wp:paragraph -->
<p>The logic is the same with the implementation using state hook. The differences are just the markup to be looped.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To see the working demo for routed react tabs, you can click the link below.</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<div class="links"><a class="link demo-link" href="https://www.devaradise.com/lab/react/react-tabs/routed-tabs" target="_blank" rel="noopener">Live Demo</a></div>
<!-- /wp:html -->

<!-- wp:heading -->
<h2>How to Implement React Tabs with <code>react-tabs</code> library</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The last way to implement react tabs is by using a library. There are a lot of tabs libraries for React out there. But here, we're going to use <code>react-tabs</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First, you should install it on your project.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="bash" class="language-bash">npm install react-tabs</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Then, import <code>react-tabs</code> to your component like below. You should also import the CSS as <code>react-tabs</code> has pre-defined style.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
```javascript
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
```

<!-- wp:paragraph -->
<p>For full implementation of react-tabs, you can see the following codes.</p>
<!-- /wp:paragraph -->

```javascript
import React from 'react'
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import 'react-tabs/style/react-tabs.css'

export default function WithReactTabs() {
  return (
    <div className="with-react-tabs">
      <h1>With react-tabs</h1>

      <Tabs>
        <TabList>
          <Tab>Tab 1</Tab>
          <Tab>Tab 2</Tab>
          <Tab>Tab 3</Tab>
        </TabList>

        <TabPanel>
          <div className="tab-content">
            <h2>Tab content 1</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>
        </TabPanel>
        <TabPanel>
          <div className="tab-content">
            <h2>Tab content 2</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>
        </TabPanel>
        <TabPanel>
          <div className="tab-content">
            <h2>Tab content 3</h2>
            <p>Here is your tab content. You can separate this as a component.</p>
            <p>Lorem ipsum dolor sit amet ...</p>
          </div>
        </TabPanel>
      </Tabs>

    </div> 
  )
}
```

<!-- wp:paragraph -->
<p>That is just a static tabs implementation. If you want to make it dynamic, just follow the mechanism in the tabs component with state hook or routed tabs. The logic is the same.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>That's all. I hope this helpful.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you want to see similar react tutorials, you might interested with these:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://www.devaradise.com/react-infinite-scroll-tutorial">React Infinite Scroll Tutorial: With and Without A Library</a></li><li><a href="https://www.devaradise.com/react-datepicker-tutorial">React Datepicker Tutorial with Top 2 Datepicker Libraries</a></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Happy coding!</p>
<!-- /wp:paragraph -->