import React, { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import "./index.css";
const Navbar = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggle = () => setIsMenuOpen(!isMenuOpen);
const ref = useRef()
useEffect(() => {
const checkIfClickedOutside = e => {
if (isMenuOpen && ref.current && !ref.current.contains(e.target)) {
setIsMenuOpen(false)
}
}
document.addEventListener("mousedown", checkIfClickedOutside)
return () => {
document.removeEventListener("mousedown", checkIfClickedOutside)
}
}, [isMenuOpen])
return (
<>
<header>
<nav>
<div className="nav">
<div className="nav-brand">
<Link to="./" className="text-black">Website</Link>
</div>
<div className="toggle-icon" onClick={toggle}>
<i id="toggle-button" className={isMenuOpen ? 'fas fa-times' : 'fas fa-bars'} />
</div>
{isMenuOpen && (
<div className={isMenuOpen ? "nav-menu visible" : "nav-menu"} ref={ref}>
<ul className="main-menu">
<li><Link to="./" onClick={toggle}>Home</Link></li>
<li><Link to="./blog" onClick={toggle}>Blog</Link></li>
<li className="drp">
<p className="dropbtn">Find <i className="fa-solid fa-angle-down"></i></p>
<ul className="dropdown-content">
<li><Link to="./find/portable-keyboards" onClick={toggle}>Portable Keyboards</Link></li>
</ul>
</li>
<li className="drp">
<p className="dropbtn">More <i className="fa-solid fa-angle-down"></i></p>
<ul className="dropdown-content">
<li><Link to="./piano-notes" onClick={toggle}>Piano Notes</Link></li>
<li><Link to="./chords" onClick={toggle}>Chords</Link></li>
<li><Link to="./tools/metronome" onClick={toggle}>Metronome</Link></li>
</ul>
</li>
</ul>
</div>
)}
</div>
</nav>
</header>
</>
)
}
export default Navbar;
Everything is working fine except the toggle button. The menu is not closing after opening it. The onClick={toggle} function is not working on the close icon. The menu will hide when someone clicks outside the menu component which is working fine. I tried a lot but didn't find any method to resolve it. Can someone try to solve this issue?
CodePudding user response:
Issue
The menu toggle button is outside the element you are attaching the outside click listener to, so when you are trying to close the menu the toggle
callback and the checkIfClickedOutside
handlers are cycling the isMenuOpen
state.
Solution
Wrap both the menu button and the menu in a div for the ref to be attached to. There is also no reason really to check if isMenuOpen
is true in the checkIfClickedOutside
handler. If isMenuOpen
is already false, enqueueing another state update to set it false is generally ignored by React. This allows you to remove it as a dependency.
Example:
const Navbar = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggle = () => setIsMenuOpen(!isMenuOpen);
const ref = useRef();
useEffect(() => {
const checkIfClickedOutside = (e) => {
if (!ref.current?.contains(e.target)) {
setIsMenuOpen(false);
}
};
document.addEventListener("mousedown", checkIfClickedOutside);
return () => {
document.removeEventListener("mousedown", checkIfClickedOutside);
};
}, []);
return (
<>
<header>
<nav>
<div className="nav">
<div className="nav-brand">
<Link to="./" className="text-black">
Website
</Link>
</div>
<div ref={ref}> // <-- ref to this containing div
<div className="toggle-icon" onClick={toggle}>
<i
id="toggle-button"
className={isMenuOpen ? "fas fa-times" : "fas fa-bars"}
/>
</div>
{isMenuOpen && (
<div className={isMenuOpen ? "nav-menu visible" : "nav-menu"}>
....
</div>
)}
</div>
</div>
</nav>
</header>
</>
);
};