I'm trying to close the open menu once an outside click is triggered. I managed to implement it on a single button but now as I want to map a list of buttons, I can't manage to open any of the button menus.
import "./navbar.css";
import React, { useEffect, useRef, useState } from "react";
import { MenuItems } from "./menuItems";
export const Navbar = () => {
const [toggle, setToggle] = useState(true);
const btnRef = useRef();
const handleClick = () => {
setToggle(!toggle);
};
useEffect(() => {
const closeDropdown = (e) => {
if (e.path[0] !== btnRef.current) {
setToggle(false);
}
};
document.body.addEventListener("click", closeDropdown);
return () => {
document.body.removeEventListener("click", closeDropdown);
};
}, []);
return (
<div>
<div className="menu1">
<i className="fa fa-home" id="home"></i>
{MenuItems.map((n, i) => (
<li key={i} className="list">
<button ref={btnRef} onClick={handleClick} className="btn1">
{n.name}
<i className="fa fa-caret-down"></i>
</button>
</li>
))}
</div>
<div className={toggle ? "d-active" : "d-inactive"}>
<div className="dropdown">Empty</div>
</div>
</div>
);
};
CodePudding user response:
My approach to this would be to have an OutsideDetector wrapper, with outsideClickHandler.
OutsideClickWatcher.js
const OutsideClickWatcher = (props) => {
const { onClickOutside } = props;
const wrapperRef = useRef(null);
useEffect(() => {
function handleClickOutside(event) {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
onClickOutside();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [wrapperRef, onClickOutside]);
return <div ref={wrapperRef}>{props.children}</div>;
};
Navbar.js
export const Navbar = () => {
const [toggle, setToggle] = useState(true);
const btnRef = useRef();
const handleClick = () => {
setToggle(!toggle);
};
return (
<div>
<div className="menu1">
<i className="fa fa-home" id="home"></i>
{MenuItems.map((n, i) => (
<OutsideClickWatcher key={i} onClickOutside={() => setToggle(false)}>
<li className="list">
<button ref={btnRef} onClick={handleClick} className="btn1">
{n.name}
<i className="fa fa-caret-down"></i>
</button>
</li>
</OutsideClickWatcher>
))}
</div>
<div className={toggle ? "d-active" : "d-inactive"}>
<div className="dropdown">Empty</div>
</div>
</div>
);
};
Why do I prefer this approach? It gives me the flexibility to pass custom outside click handlers, and it's very reusable for all sorts of ui elements.
Note that this is a boilerplate code, it can obviously be improved further.