Add a Hamburger Menu to React
Table of Contents
§ Creating the Menu
The first thing we'll do is create the menu, using a JSON object and the map
function.
We'll create the JSON object at the beginning of the page:
const TopAdBannerData: TopAdBannerDataProps[] = [
{
menuItem: 'Home',
url: '/',
},
{
menuItem: 'Search',
url: '/search',
},
{
menuItem: 'Contact',
url: '/contact',
},
{
menuItem: 'Portfolio',
url: '/portfolio',
},
{
menuItem: 'Résumé',
url: '/resume',
},
{
menuItem: 'Blog',
url: '/blog',
},
];
Then have it repeat in a function in the HTML section. Notice that we'll use ARIA to designate the list as a menu. (For this example, we are using an animated hamburger icon from Jonsuh).
// create a full menu enclosure
<div className="adNavAnchor position-relative align-items-center">
// insert your favorite hamburger menu icon
<div
className={
// utilize isActive boolean to animate hamburger
'hamburger hamburger--spin ' + (isActive ? 'is-active' : null)
}
onClick={handleToggle} // handleToggle changes the isActive boolean
tabIndex={0}
aria-expanded={isActive ? true : false}
aria-controls="mobileMenu"
aria-haspopup="true"
>
<div className="adNavHam hamburger-box">
<div className="hamburger-inner"></div>
</div>
</div>
<ul // dropdown menu begins
className={
'adNavDrop position-absolute list-unstyled user-select-none ' +
(isActive ? 'adNavDrop--show' : null) // class changes with isActive boolean
}
id="mobileMenu"
role="menu"
aria-labelledby="menubutton"
>
{items.map((item, index) => { // menu is created const { menuItem, url } = item; return ( <li key={index} role="presentation"> <a className="d-block" href={url} role="menuitem" onClick={() => handleMenuItemClick(url)} > {menuItem} </a> </li> ); })} </ul>
</div>
§ Styling the Menu
For your dropdown menu, it's important to anchor the div
to the hamburger menu. Ensure the hamburger menu element has a position property and make the menu position itself relative to this by using the absolute
value for the position property.
For the purposes of this example, we are using styles from bootstrap. position-absolute
and position-relative
are Bootstrap classes we are using in the code above, so we don't need it in the SCSS code here.
.adNavAnchor {
display: block;
visibility: visible;
@include media-breakpoint-up(md) {
display: none;
visibility: hidden;
}
}
.adNavHam {
margin: 6px 10px 0 10px;
}
ul.adNavDrop {
right: 0;
top: 52px;
visibility: hidden;
opacity: 0;
transition: visibility 0s linear 0.11s, opacity 0.11s linear;
&--show {
visibility: visible;
opacity: 1;
z-index: 300;
transition-delay: 0s;
box-shadow: var(--box-shadow);
}
li {
a {
background: var(--primary-background);
padding: 0.2rem 2rem;
border: 1px solid var(--secondary-background);
text-decoration: none;
font-size: 1.1rem;
color: var(--primary-contrast-color);
&:hover {
text-decoration: underline;
}
}
}
}
Since this is a React example, we are using hooks to show and hide the menu when clicked.
const [isActive, setActive] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const handleToggle = () => {
setActive(!isActive);
};
const handleMenuItemClick = (url: string) => {
setActive(false); // Close the menu
window.location.href = url; // Navigate to the URL
};
§ Menu closes when clicked outside
Another important feature is to make it so that when users click outside the menu, it closes. We do this by creating a useEffect
which detects when a user is clicking outside the menu, which we have earlier marked with const menuRef = useRef<HTMLDivElement>(null)
. We then return a simple event listener.
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
menuRef.current &&
!menuRef.current.contains(event.target as Node) &&
isActive
) {
setActive(false);
}
};
document.body.addEventListener('click', handleClickOutside);
return () => {
document.body.removeEventListener('click', handleClickOutside);
};
}, [isActive]);