The Simplest Way To Create A Reusable Tab Or Linked Tab System On React

Davit Vardanyan
The Startup
Published in
7 min readMay 22, 2020

Tabs (Introduction)

We never hesitate to sacrifice more lines of code, when it comes to a bit better UX. Actually in some cases, a simple tab realization can bring a huge UX improvement, especially on a mobile-first project, being one of the best ways to avoid unnecessary routes or pages. Even more, in some cases it can be the best way of navigation, or simplify the user flow.

Tabs solution preview

What we have (3 main solutions)

CSS libraries or frameworks. This is one of the simplest way to create a tab component. Almost all Bootstrap kind of big frameworks provide tab solutions, but if you have a lot of customization, it would be a pain. Or what if you don’t actually use such a framework on your project.

Ready tab solutions. This is a simple way too, but you have to dig into docs to customize it, and in most cases you can’t customize the logic or scale it, or it is possible but you waste your time fixing bugs. There aren’t any super popular libraries, but still you can find a lot of solutions. An example you can check out is react-tabs.

Write a simple solution which can be customized, scaled, and reused not only on the current project. That’s what we’ll do together.

About this post (the goal)

This post has 2 goals, the first goal is to share a very simple tab solution. The second goal is to show how we can make a component highly reusable, easy to use in other projects, how we can scale the functionality of such a component without loosing the reusability.

Now, we’ll design it step by step. If you just need the code, feel free to copy from demo (Codesandbox, Github) without reading the step by step explanation. You can come back anytime and read it if you need to understand any part.

CodeSandbox demo of the Tabs and LinkedTabs

Let’s start

First, let’s create a task — create a problem and solve it. Say we have a video streaming app, where we have a user’s Channels page, we have a component of user’s “Subscribed” channels, and we also have a component where we show channel recommendations, let’s call it “Recommended” channels. And for the next version of our app, the UX engineer decided to place a “Recommended” section with the “Subscribed” section. And decided to navigate from one to the other by a tab bar switch.

Channels page component

First, let’s imagine the Tabs usage skeleton in the component where we have those sections.

The Channels page, where we’ll use our Tabs, and this is the usage skeleton yet

Cool, that’s how we will use and reuse our tab system. We have a <Tabs /> component, which has a child component named <Tab />. And that <Tab /> component takes the future tab’s “label”, the tab’s “name”, and the content which is shown conditionally when tab is the active one.

Tabs component

Now let’s start to design our reusable <Tabs /> component. We do it by creating a functional component and then do props object destructuring to have its children prop.

Tabs component (props destructured)

Honestly it is not a component yet, because it doesn’t return anything. So let’s design the html skeleton of our future tabs. Let’s say we will have a div which will contain two containers, one for tabs, the other for the tab’s content (in our case “Recommended” and “Subscribed” components), and each one will be shown conditionally depending on the active tab.

Tabs component html markup changes
Tabs component after changes

Now let’s take care about logic itself. Our reusable component will have a state to hold the active tab’s label, and initially will contain the first child’s label (AKA children[0].props.label). Also a memoized handler which changes the active tab.

Tabs component state and handler changes
Tabs component after changes

It’s time to dynamically define our tabs and tabContent.

To do that for the tabs we iterate the children, and for each child we create an html button element. It has an onClick which takes the ”activeTabHandler”. And we conditionally add the active class to the button’s classname.

For the tabContent, we iterate the children again, this time we filter it, and only hold the child which has the same label as the activeTab.

Tabs component tabs and tabContent changes
Tabs component after changes

Awesome, we have our reusable <Tabs /> component, and now we come back to <Tab /> child component, which also has a child (in our case it would be the “Recommended” section’s component of our video streaming app, also we will use it for the “Subscribed”).

It is very simple functional component, it just returns its children (props.children).

Tabs component after adding a Tab component, the full picture is shown after styling

Styling

Now let’s put those styles into Tabs.css file, and finalize our solution.

Putting all together

We have placed our two reusable components in a Tabs.js file. Then we have imported it in our “Channels” component. (Don’t forget about imports, also export those two components from Tabs.js).

Now we use it just as we did the skeleton at the beginning.

Voila, we have a reusable “Tabs”. And we can use it in any project (just modify styles if we need)

Now we reuse and scale the functionality

Let’s say, now we use it in another project - Event web app, which uses React Router. Our Event web app has exemple.com/events page, and we want to give query addresses to our tabs - to open a tab when go to a url address like exemple.com/events?tab=upcoming

Well, it is also simple, we just need to add a few of lines in the Tabs component.

First let’s rename and call it LinkedTabs. (Tabs.js > LinkedTabs.js)

Events.js

Events is a component which represents /events page. To do not loose our LinkedTabs reusability or make it complicated, we will get the query in the component which is used on the example.com/events route (Events.js), and pass it to the LinkedTabs (AKA Tabs component) component as a prop.

Events component changes for the query
Events component after changes

To get the query we can use the browser URLSearchParams API. This API helps us to get the query params. So if we want to get the value (“upcoming” in this case) of the “tab” in “/events?tab=upcoming” string, we do as shown above.

But this was hardcoded query address, to get it dynamically for each tab we can use props.location.search (provided by React Router). (Note. Make sure you have passed props from React Router to the Events component when you made the /events route, if any problem with this, check out our demo’s App.js file or follow the official React Router docs)

So, in events component we set the props.location.search in URLSearchParams as an argument instead of hardcoding /events?tab=upcoming.

LinkedTabs.js

In LinkedTabs component we will check if we have a tab named like tabParam (Remider: we passed it from Events.js as a prop, it is the query (param) value like “upcoming”). If we don’t have such a tab (means user typed a wrong param like exemple.com/events?tab=asdfg ), we select the first tab as an activeTab. If user typed a right address, so we have a tab named like tabParam, we select it initially as an activeTab. We make it by doing the following in LinkedTabs component.

LinkedTabs component changes
LinkedTabs after component

And voila again, now you can type /events?tab=upcoming, and if you have a tab with a label “upcoming”, you will be right on that tab. So, our LinkedTabs is ready, and it is reusable as much as the previous simple Tabs solution.

Adding a bit more functionality

If we need to see the tab’s address at the browser’s address line when we click on the tab, in LinkedTabs component we can change the button html element to a React Router Link, and set it up to our needs. Also, for the reusability in the Events component pass the route name to the LinkedTabs.

Events component, passing the route name to the LinkedTabs
LinkedTabs component changes, to see the tab’s link on the address

Open the CodeSandbox demo bellow, and and navigate in files to watch on LinkedTabs and Events component, also check out the demo, and compare with yours.

CodeSandbox demo

Markup and structure

Before we finish, let’s review the DOM markup of our solution. Say we have 3 tabs.

Tabs UI

We have a container down bellow, which contains two blocks, one for tabs, the other container for the current tab’s content. This container doesn’t have rendered all tabs contents at once. The content is rendered conditionally depending on the active tab.

DOM structure

Here you see the DOM changes when we switch from 1st tab to 3rd. The previous tab’s content is removed from DOM, and the current tab’s content is rendered.

DOM changes

OUh, That’s the end. If you have an issue, you can note it on the Github repo bellow. Here’s references.

Tabs and Linked Tabs solution’s Github repo

CodeSandBox demo

React official docs

React Router official docs

--

--

Davit Vardanyan
The Startup

I am a dentist as a specialist. And a developer since childhood. I ❤ Browser & I ❤ Node.js