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:
- Create a Dynamic Route
- Create the routes for the components (Tabs definition)
- Find the component that is matching the route
- Render the component
- 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
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.
Top comments (8)
Awesome thanks. I have found other articles as well which were great. THough the reduced the number of lines it was hard to understand. This though with boilerplate, its clear as to what is going on and easy to customize.
can you plz share the code
U got code ?
Hi. I do not have a git repo as such. All the code that I have used is provided here. The last code snippet in the article is the complete code.
Thanks
Appreciate you man the way you explained the pain with wonderful humor , commenting before reading full can't stop well versed
Wow this is very smart
Some comments may only be visible to logged-in visitors. Sign in to view all comments.