#typescript

#javascript

#tabview

#design system

#next.js

#routing

Calendar

Sunday, 5 May, 2024

Book

11 mins read

Eyes

137 reads

code block

PROGRAMMING

Creating a TabView Component using Parallel Routing in Next.js

Creating a TabView Component using Parallel Routing in Next.js

Background


TabView is one of the most popular UI elements found across a plethora of apps and websites. In this tutorial, I am going to explain how to develop a custom TabView component using Next.js and parallel routing supported by the framework.

React and Next.js both communities are moving towards what's called Server Components. Next.js v13.0 was truly transformational in the way things were done and first step towards this new approach. Parallel routing is what was introduced in this new architecture in the Next.js world. For this tutorial however, I will keep things simple, and use Client Components.

We will be using TypeScript as our choice of language. But first, let's learn a more about TabView and why to use it?

What is a TabView?


As explained on Material Design home page:

"TabViews are used to organized content across different screens and views."

There are some properties that need to be satisfied in order to make an efficient TabView element on UI:

  • Use tabs to group content into helpful categories.
  • Two types: primary and secondary.
  • Tabs can horizontally scroll, so a UI can have as many tabs as needed.
  • Place tabs next to each other as peers.

But where do we use TabViews? In a lot of places to be honest. But here are a few popular examples:

AirBnb using Tabs on their home page.

1

ClickUp using TabView on their landing page.

2

Dribbble also uses TabView on their home page.

3

MongoDB uses TabView to make navigation easier for a developer.

4

The Need for a Tab Bar and Tabbed View.


By now it should be pretty clear that TabView is an important element in a UI. Like other elements in a UI design TabView helps us achieve a few things enumerated below:

  1. TabView is great at navigation. This is also one of the most popular reasons you find TabViews heavily used especially in smaller devices (mobile phones, tablets, etc.)
  2. They are great at saving space on the UI and giving contextual navigation to the user.
  3. TabView are great at keeping related content together. They are also great to provide categorical navigation to the user. Imagine a sports store using them to provide a more categorical shopping experience.
  4. Bottom Navigation Bar, which is yet another popular UI element especially on mobile devices is inspired by TabBar.

You can read more about TabViews on the following links:

Setting Up the Project

Enough talking about the design aspect of TabBar. Let's move to the development part of it. Shall we? Let's quickly set-up a working Next.js project.

Run the following code on your terminal:

1npm create-next-app@latest tab-bar-view

What is parallel routing in Next.js?

A side note before we begin: you can achieve the same functionality using children props feature of React. Although, the functionality achieved will be similar, doing it with parallel routing has some added advantages over using children props. Although, These advantages are very specific to Next.js, which I will discuss in the end.

Let us imagine that there is a analytics dashboard. On this dashboard page, there are four different sections, that show four different statistical data to the end user. To make life more complex, let us say that each of this section uses a different API from the backend, and different statistical algorithms to render the data.

There are two ways to render this dashboard page. First the simpler way, you make API call to all four APIs, perform calculations for all four data (maybe in parallel to load faster) and once all the data is available to us, we simply render the UI.

Now imagine if the load time and algorithmic complexity of one of the four APIs is very fast compared to the others, shouldn't we fetch this data as soon as it is ready and render at least the partial UI. Parallel routing helps us solve this exact problem. It helps you render different parts of a UI as and when they are available. The best part, each component in UI is then separated from the other, and you can lazy load the entire page with these components as and when their respective data is ready to be rendered.

Parallel Routing in Next.js

In Next.js, parallel routing is achieved using a concept called Slots. Next.js routing architecture is heavily based on the file and folder names. Slots are just one another fancy term for one of the special conventions in file and folder naming.

You need to understand how to render pages and layout and in the newer version of Next.js.

To create slots simply create folders starting with '@' and then the name of the UI layout in your page. For our dashboard example, the four UI components and their respective folder names will be:

  1. trips (@trips)
  2. amounts (@amounts)
  3. ratings (@ratings)
  4. points (@points)

Then we create a file called page.tsx inside each of these folder. This file is a normal TSX file that contains UI code. Our slots are ready to be used. But how do you use them? Simply pass them as children prop to the parent layout.tsx file in the folder. For our dashboard example, if the folder structure looked like this:

app
+-- layout.tsx
+-- @trips
|   +-- page.tsx
+-- @amounts
|   +-- page.tsx
+-- @ratings
|   +-- page.tsx
+-- @points
|   +-- page.tsx

We need to pass all our slots in the parent layout.tsx file like this:

1'use client'; 2 3export default function Layout({ 4 trips, 5 team, 6 analytics, 7}: { 8 children: React.ReactNode 9 analytics: React.ReactNode 10 team: React.ReactNode 11}) { 12 return ( 13 <> 14 {children} 15 {trips} 16 {amounts} 17 {ratings} 18 {points} 19 </> 20 ) 21}

Notice how each slot becomes a children element inside the layout file.

File Architecture

We are going to make TabView that works like shown below:

Visiting document mentioned @ Next.js official docs to create a TabView we understand quickly that the functionality almost remains same if we want to implement a TabView.

We make sub-folders inside a parent folder. The parent folder is actually a slot and the sub-folders represent the tabs we want to create. This time the only difference is that inside the parent folder we create a layout.tsx file that will be responsible to provide the shared TabView element to all the children. This layout.tsx file will also take care of routing and rendering the content according to the TabBar item selected.

So, let us start implementing our TabView now.

Let's create a folder called @tabs inside the app folder. Inside @tabs folder let us create three folders called flights, trips, and explore. Let us create a layout.tsx file inside the @tabs folder and page.tsx inside each of the three sub-folders we created.

Our project structure should look similar to this:

app
+-- layout.tsx
+-- page.tsx
+-- @tabs
|   +-- layout.tsx
|   |   +-- flights
|   |   |   +-- page.tsx
|   |   +-- trips
|   |   |   +-- page.tsx
|   |   +-- explore
|   |   |   +-- page.tsx

Notice a layout.tsx file inside the app folder. That is a must-have file which Next.js requires in order to render your UI. Removing this file, will result in a crashing of your app.

Creating the TabView, Finally?

Let us create a folder called components and then a sub-folder called tab-view. Inside tab-view folder, create three files called:

tab-view-props.ts

1export default interface TabViewProps { 2 tabs: { 3 id: number; 4 label: string; 5 }[]; 6 rootLink: string; 7}

The first field called tabs contains the data for the tabs to render via the TabBar. The second field called rootLinks is used to automatically change the link and route the user to first tab whenever we navigate to any layout that has a TabView. For our example, this will automatically route the user to the filghts tab and link.

Next let us create the tab-view.tsx file that contains the logic for rendering the TabBar and view:

1'use client'; 2 3import {usePathname, useRouter } from 'next/navigation'; 4import { useEffect, useState } from 'react'; 5 6import TabViewProps from './tab-view-props'; 7 8import { Subtitle } from '../typography'; 9 10import styles from './styles.module.scss'; 11 12export default function TabView(props: TabViewProps) { 13 const [activeTab, setActiveTab] = useState(props.tabs[0].label); 14 const router = useRouter(); 15 const pathname = usePathname(); 16 const pathnameArr = pathname.split('/'); 17 18 useEffect(() => { 19 /** 20 * Whenever a {@link TabView} is first called, redirect the root layout to first child appended to the pathname. 21 */ 22 if (pathnameArr.at(-1) === props.rootLink) { 23 router.push(`${pathname}/${props.tabs[0].label}`); 24 } 25 setActiveTab(pathnameArr.at(-1)); 26 }, []); 27 28 /** 29 * This function handles the click event on any Tab of a {@link TabView}. 30 * 31 * @param tabData An object containing information about the tab clicked. 32 */ 33 const handleTabClick = (tabData: { id: number; label: string }) => { 34 setActiveTab(tabData.label); 35 router.push( 36 pathnameArr 37 .slice(0, pathnameArr.length - 1) 38 .concat(tabData.label) 39 .join('/') 40 ); 41 }; 42 43 return ( 44 <nav className={styles.tabBar}> 45 {props.tabs.map((tabData) => ( 46 <button 47 key={tabData.id} 48 className={ 49 activeTab === tabData.label 50 ? styles.tab__active 51 : styles.tab__inactive 52 } 53 type="button" 54 onClick={() => handleTabClick(tabData)} 55 > 56 <Subtitle 57 textColor={ 58 activeTab === tabData.label ? '#141414' : '#505050' 59 } 60 width="100%" 61 > 62 {tabData.label} 63 </Subtitle> 64 </button> 65 ))} 66 </nav> 67 ); 68}

Forget the Subtitle component. That is just a custom Text component.

Let us understand the what this code does. First we simple map the tabsData passed to us in the props and render a button with a label for each tab.

We set the className on the each button and activeColor property on Subtitle based on whether the a particular tab item is active on inactive which will be later used in styling the components.

Then we have a function called handleTabClick, which essentially does two things:

  1. Sets the active tab item based on the user click.
  2. Changes the URL so that we can route to the proper component inside the sub-folder created in @tabs folder. Proper routing means we render the correct UI for each tab item.

TabView Styles - Watermelon Sugar High!

Create a file called styles.module.scss inside the tab-view folder.

styles.module.scc

1.tabBar { 2 display: flex; 3} 4 5%tab { 6 height: 48px; 7 flex: 1; 8 background-color: #F7B6D4; 9 border: none; 10 cursor: pointer; 11} 12 13.tab__inactive { 14 @extend %tab; 15} 16 17.tab__active { 18 @extend %tab; 19 20 border-bottom: solid 2px #B0005C; 21} 22 23.tabContent { 24 padding: 32px 80px; 25}

A pretty simple SCSS file. We make our tab bar a flex display so that we can accommodate multiple items inline.

We give each tab item a flex of 1, to take equal space in the TabBar irrespective of the number of items.

Some padding, height and other basic CSS attributes to style our TabBar look like the one shown earlier. One last thing to note is that whenever any tab item becomes active, we simple add a bottom-border to the button to give a feel of indicator to the user.

Layout

The final piece in the puzzle is to write the logic in our layout files. First let's work on the main layout.tsx file inside our app folder.

1'use client'; 2 3import { ReactNode } from 'react'; 4 5import './styles/global.scss'; 6 7export default function MainLayout({ tabs }: { tabs: ReactNode }) { 8 return ( 9 <html lang="en"> 10 <body> 11 <div>{tabs}</div> 12 </body> 13 </html> 14 ); 15}

Pretty straight forward. The root layout file in Next.js needs a mandatory <html> or <body> tag around the main content. Here inside the <body> tag we are passing tabs as children props. Don't get confused here. tabs is just a variable name here. In Next.js documents you will find this variable named children.

Now the layout.tsx file inside @tabs folder.

1'use client'; 2 3import { type ReactNode } from 'react'; 4 5import TabView from '../../components/tab-view/tab-view'; 6 7export default function TabLayout({ children }: { children: ReactNode }) { 8 const tabsData = [ 9 { 10 id: 0, 11 label: 'flights' 12 }, 13 { 14 id: 1, 15 label: 'trips' 16 }, 17 { 18 id: 2, 19 label: 'explore' 20 } 21 ]; 22 23 return ( 24 <div> 25 <TabView tabs={tabsData} rootLink="accounts" /> 26 {children} 27 </div> 28 ); 29}

We first create an array containing objects respective to each tab item that we want to render. Because we are iterating over this array in our TabView implementation, we need keys to uniquely identify each tab item in the array. The id field is used for the same exact reason.

Then we render our TabView component passing in the tabsData array. Also, remember, handleTabClick function earlier and its second functionality to automatically route the user to correct URL, whenever any Tab View is opened first. It does with the help of a root link passed in the rootLink prop.

A quick hack: Whenever you are working with arrays in React, always give them a default id attribute, be it a number index or string based.

Children vs Parallelism

Remember I told you that you can achieve the same functionality using children props instead of parallel routing. If you notice, you will understand that even parallel routing is similar to children props as it also renders all the tab items content by passing them as children props to the parent layout component.

Then why choose one over another? Let's see:

  • Children props are great way to pass an entire component to a parent component without worrying about the implementation of children component. It is actually an amazing concept and used in HOC too, which is an advanced React composition pattern.

  • The only advantage I see in using parallel routing is that you conform to the notion of Next.js. Even if we use children props, we need to create separate files for each tab item in their respective TSX files. So in terms of code, I don't think there is much difference.

  • But creating separate folders inside a slot folder, and letting Next.js take care of the children prop rendering helps us achieve two things in specific:

    • Separation of concerns: Rather than us taking decision to naming the folders and files that store our content logic for each tab item, it is much easier to work with Next.js hierarchical structure. Albeit, it might look intimidating as the number of pages with Tab View grows but that fear is permanent in all large codebases.
    • Imperative Programming: We can focus on actual content and its logic rather than worrying about how to render a TabBar and its content.

In the end, it's a personal choice but I prefer using Next.js parallel routing as it can also help us achieve dashboard kinda UI in future if needs arise using all the awesomeness that Next.js provides out of the box. Achieving similar functionality using children props would be a bit harder in comparison.

That's all. We have successfully implemented a TabBar using parallel routing in Next.js.

You can also check the final working code on GitHub @ Why-So-Serious-Life/create-tab-bar-parallel-routes-nextjs

If you want to learn how to create your own TextField using React.js, you can checkout Create a custom TextField Component with Floating Labels.

Conclusion

  • We learned about the TabViews and TabBar in UX design. Importance and popularity of the element and how can we enhance UX of a app using the TabView element.
  • We learned how parallel routing works in Next.js and where will you use it in a front-end application.
  • We also learned how to implement TabView using parallel routing in an Next.js application.

Finally, I want to leave you with a thought.

What is life if not a drink in different bars?

Share on social media...

WhatsApp LogoInstagram LogoFacebook LogoTwitter LogoLinkedIn LogoSnapchat LogoSignal LogoSlack LogoTelegram Logo
Check out the Programming category
code block
PROGRAMMING

Deciphering the Code: Where Brackets Are Puzzles, and Debugging Is Detective Work! Welcome to the world of programming, where lines of code create digital magic. Whether you're a seasoned coder or a newbie, get ready to explore the syntax symphony, tackle bugs like a pro, and embark on quests to build the next big thing in tech. 💻🚀

Document

2 articles

Eyes

158 reads

TOP READS
table top at bar

Creating a TabView Component using Parallel Routing in Next.js

Eyes

137 reads

programming

laptop screen with javascript code

Set Up Node.js Project With TypeScript and nodemon

Eyes

22 reads

programming

MORE IN PROGRAMMING
table top at bar

Creating a TabView Component using Parallel Routing in Next.js

Eyes

137 reads

programming

laptop screen with javascript code

Set Up Node.js Project With TypeScript and nodemon

Eyes

22 reads

programming

TOP CATEGORIES
code block

Programming

Document

2 articles

Eyes

158 reads

luggage

Travel

Document

0 articles

Eyes

0 reads

bulb with gear

Thoughts

Document

0 articles

Eyes

0 reads

tennis ball

Sports

Document

0 articles

Eyes

0 reads

books

Reading

Document

0 articles

Eyes

0 reads

target

Productivity

Document

0 articles

Eyes

0 reads