How to create Nested Tabs Layout with Routing in NextJS?

harisahmadev

Haris Ahmad

Posted on May 4, 2021

How to create Nested Tabs Layout with Routing in NextJS?

The pain 😳

"I will not repeat myself". This is what I vowed to me this morning and every morning and here I am copying and pasting the same code for the dashboard tabs to all my pages. What a shame. It might be because I could not find if NextJS allows you to have layout in dynamic routing without touching _app.js and the ways I found to do it were a little hacky. From time to time, we developers have to remind ourselves that God has created us for a purpose higher than Ctrl+C & Ctrl+V and still ending up getting an error and cursing the stack-overflow AnonymusUser034 because pasting his code in my code-base has caused an error at line 983 of file i_have_no_idea.jpg. At that point we need to realize that we have to turn to our original job, which is to be creative. Well, enough pain inflicted, let us dive right into the solution. Although you might still Ctrl+C from here.

The Antidote πŸ’‰

The solution in one line

We are going to use dynamic routes query and useRouter hook to map the route paths with the ReactJS components. Didn't make sense? I thought so. It is because you can't put an elephant in a cookie-jar without killing it. So lets not kill our work-ethic and do some hard work.

The solution in more lines

In the following the steps we are going to follow are listed:

  1. Create a Dynamic Route
  2. Create the routes for the components (Tabs definition)
  3. Find the component that is matching the route
  4. Render the component
  5. Create Navigation UI Elements

1.Create a Dynamic Route

Creating a dynamic route is fairly simple. You just have to create a .js file in pages directory with the filename inside []. An example would be [route].js. If this does not make sense to you, either you are at the wrong article or NextJS is as much alien to you as aliens are to us. You can visit Next JS Docs if you want to learn more about NextJS. Our file-tree should look like this

src
 |_pages
  |_[route].js

Enter fullscreen mode Exit fullscreen mode

2.Create the routes for the components

To represent the routes, we are going to use simple Javascript objects. We will put all the objects inside an array so we can map them later. So our [route].js would look something like this (which is wrapped inside at React component "NavBar").

const NavBar = ()=>{
  const routes = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

}
Enter fullscreen mode Exit fullscreen mode

slug: The part of URL at which you want to display your component. Like users in myamazingsite.com/users is a slug.
label: The Label you want to display on a menu item which will represent this component.
component: The React Component that will be rendered for this path.

3. Find the component that is matching the route

Now we will find one of the components inside the routes array which matches the requested path. For example if someone enters localhost:3000/members we will find in the routes array an item that has slug: 'members'. For this purpose, we have to know what user has requested in the URL

useRouter

We will use useRouter hook from next/router module which will allow us to see what URL is there in the address bar. To get the URL we will:

import {useRouter} from 'next/router'

const NavBar = ()=>{
  const components = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

  // -->
  const router = useRouter()
  currentPath = router.query.route
}
Enter fullscreen mode Exit fullscreen mode

router.query.route: router.query will give us an object that contains all the route's paths requested. The end part .route corresponds to the file name [route].js.It would store in itself the requested path. if the path is localhost:3000/members, router.query.route will return string 'members'. The filename kind of acts like a variable. If you had filename as [dummy].js Then you would request router.query.dummy

Finding the component

To find the object in routes array which have a slug matching the currentPath, I spare you with your fancy algorithms to iterate over the array, but I am going to use a simple approach. I am going to use Array.find() function.

import {useRouter} from 'next/router'

const NavBar = ()=>{
  const routes = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

  const router = useRouter()
  currentPath = router.query.route

  // -->
  const findSlugMatchingCmp = ()=>components.find((cmp =>{
        return cmp.slug === currentPath
    } 
  )

  useEffect(()=>{
    const foundComponent = findSlugMatchingCmp()
  }, [router])
}
Enter fullscreen mode Exit fullscreen mode

I am using useEffect hook because getting a route using useRouter hook is asynchronous operation. As you can see in the dependency array of useEffect we depend on the change in useRouter

What if user is naughty and he enters localhost:3000/hehe path which doesn't exist?

We will redirect him

import {useRouter} from 'next/router'

const NavBar = ()=>{
  const routes = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

  const router = useRouter()
  currentPath = router.query.route

  const findSlugMatchingCmp = ()=>components.find((cmp =>{
        return cmp.slug === currentPath
    } 
  )

  useEffect(()=>{
    const foundComponent = findSlugMatchingCmp()

    // -->
    if(currentPath && !foundComponent)
        router.push('/404')
  }, [router])
}
Enter fullscreen mode Exit fullscreen mode

4. Render the component

Now that we know what component matches with the slug, we will go ahead and return it for rendering

import {useRouter} from 'next/router'

const NavBar = ()=>{
  const routes = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

  const router = useRouter()
  currentPath = router.query.route

  const findSlugMatchingCmp = ()=>components.find((cmp =>{
        return cmp.slug === router.query.route
    } 
  )

  useEffect(()=>{
    const foundComponent = findSlugMatchingCmp()

    if(currentPath && !foundComponent)
        router.push('/404')
  }, [router])

  // -->
  const cmp = findSlugMatchingCmp().component

  return (
    <div>
      {cmp}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

5. Create Navigation UI Elements

I will leave it upto you if you either want Tabs, SideMenu, Navigation bar, or whatever navigation component you want to implement. For the demo I am going to use NextJS Link component because it is relatively simple. We just have to redirect user to the correct path when user click on an element from our UI navigation. e.g

import {useRouter} from 'next/router'

const NavBar = ()=>{
  const routes = [
    {
      slug: 'members',  
      label:'Organization Members', 
      component: <MemberTable />
    },
    {
      slug: 'drivers-search', 
      label:'Driver Pre-Screen', 
      component: <DriversSearch />
    }
  ]

  const router = useRouter()
  currentPath = router.query.route

  const findSlugMatchingCmp = ()=>components.find((cmp =>{
        return cmp.slug === router.query.route
    } 
  )

  useEffect(()=>{
    const foundComponent = findSlugMatchingCmp()

    if(currentPath && !foundComponent)
        router.push('/404')
  }, [router])

  const cmp = findSlugMatchingCmp().component

  return (
    <div>
      // -->
      <div>
        {routes.map(route=>(
          <Link href={route.slug}>{route.label}</Link>
        ))}
      </div>
      {cmp}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

We have created a nested next route with layout by using dynamic routes query and useRouter hook to map the route paths with the ReactJS components.

πŸ’– πŸ’ͺ πŸ™… 🚩
harisahmadev
Haris Ahmad

Posted on May 4, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related