How to create Nested Tabs Layout with Routing in NextJS?
Tags: nextjs, routing
Published: 2021-05-04 23:03:04

# 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](https://nextjs.org/) if you want to learn more about NextJS. Our file-tree should look like this
```
src
 |_pages
  |_[route].js
    
```  

### 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 />
    }
  ]
  
}
```
**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
}
```

**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])
}
```
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])
}
```
### 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>
  )
}
```
### 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>
  )
}
```
### 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.