Home > Mobile >  Menu won't open on click (ReactJS)
Menu won't open on click (ReactJS)

Time:07-19

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.

  • Related