Hiding Components on Page Change in Next.js
It’s a common behavior to close a navigation menu, sidebar or pop-up while navigating to a new page.
But, it’s also a common problem in Next.js to see the menu remain open after navigating.
So, how do you hide a component while navigating between pages?
The answer is to use React’s useEffect
hook. Let me show you how.
Hiding a Component After Route Change
Here’s a a basic example of a menu component that remains open after page change.
import { useState } from 'react';
export default function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
function setIsOpenValue(value) {
setIsOpen(value);
}
return (
<>
<button onClick={() => setIsOpenValue(true)}>Open menu</button>
<nav className={`${isOpen ? 'open' : 'close'}`}>
<button onClick={() => setIsOpenValue(false)}>
Close menu
</button>
<Navigation />
</nav>
</>
);
}
It uses isOpen
state to determine if the menu should be visible or not. When the menu is open, it renders a Navigation
component that contains links to other pages. But, clicking a link and opening another page keeps the menu.
To fix this problem, you need to change your component’s state when the route has changed.
You can do so by using useEffect
hook and adding asPath
property from useRouter
as a dependency.
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
export default function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const { asPath } = useRouter();
useEffect(() => {
setIsOpen(false);
}, [asPath]);
// ✂️
}
Why use asPath
and not pathname
instead? It is better to use asPath
over pathname
, because pathname
doesn’t change while navigating between dynamic routes.
If you go from /categories/react/
to /categories/nextjs/
, the useEffect
hook won’t fire, because pathname
remains the same —/categories/[name]
. But, asPath
always reflects the path the way you would see in your browser.
Hiding Components Using Router Events
Alternatively, you can register router events inside useEffect
to achieve the same result.
You need to create a handler function that will hide your component, and register it on routeChangeComplete
event.
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
export default function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useEffect(() => {
const handleRouteChange = () => {
setIsOpen(false);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router]);
// ✂️
}
Your useEffect
hook should return
a function that removes your handler function from the routeChangeComplete
event.
If you want your component to disappear immediately when navigation begins, you can use routeChangeStart
event instead.
- router.events.on('routeChangeComplete', handleRouteChange);
+ router.events.on('routeChangeStart', handleRouteChange);
return () => {
- router.events.off('routeChangeComplete', handleRouteChange);
+ router.events.off('routeChangeStart', handleRouteChange);
};
This will close the menu after clicking a link, even before the navigation has finished.