How to close navbars on route change in Next.js
In React, top level elements that rely on state that affect how they appear on screen can face some unique challenges, such as managing that state change between route cycles.
Recently I was asked to create a basic ecommerce site for a friend and was faced with a challenge along these lines. When in a mobile layout, the user toggles the navbar to display a list of menu items. Clicking on a nav item routes the user to that path, but leads to unwanted behaviour where the user then has to close the menu on each re-route:
The desired experience is to have the navbar ‘untoggle’ on each re-route.
It turns out in Next.js this is extremely simple Next.js Router.
We use the useRouter function to detect when a new route has occurred, and once that re-route has occurred, we can utilise useEffect()
to run a component render side effect to close the menu.
First, we start with our default MobileNav
component, which has an event listener for clicks on the hamburger icon, and which manages state to dynamically set the class of the drop down menu itself:
function MobileNav() {
const [menuIsOpen, toggleMenuIsOpen] = useState(false);
function handleHamburgerClick() {
toggleMenuIsOpen(!menuIsOpen);
}
return (
<nav className={s["mobile-nav"]}>
<HamburgerMenu onClick={handleHamburgerClick} />
<ul className={menuIsOpen ? s.active : undefined}>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/shop">Shop</Link>
</li>
<li>
<Link href="/gallery">Gallery</Link>
</li>
<li>
<Link href="/custom-builds">Builds</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
<li>
<Link href="/contact">Contact</Link>
</li>
</ul>
</nav>
);
}
The first step is to add import and hook into the MobileNav
useRouter()
function.
Import useRouter:
import { useRouter } from "next/router";
Access useRouter by storing it in a “const“
within the component function itself:
function MobileNav() {
const [menuIsOpen, toggleMenuIsOpen] = useState(false);
const router = useRouter();
Next we will implement useEffect()
to hook into our component’s life cycle and detect when the DOM updates.
Our useEffect()
function call will use our router variable as its only dependency. This means every time the router changes, we will detect this change and run a side effect within the MobileNav
component to update the state of menuIsOpen
.
Import useEffect
import { useEffect, useState } from "react";
Add the useEffect()
hook:
useEffect(() => {
toggleMenuIsOpen(false);
}, [router]);
Now everytime a route change is detected, the MobileNav
component will detect that route change and update the menuIsOpen
state to false, closing the menu: