Detecting clicks outside a div and toggle menu

Viewed 82

I have a toggle menu and I'm trying detect the click events outside of a menu to be able to close the menu, I can close the menu when user clicks outside of the menu, however to open the menu again you would have to click on it twice, does anyone know what I have to do to fix that, (the menu should open with one click)

const RightMenu = ({ t, history }) => {
  let [menuOpen, setMenuOpen] = useState(false);

  const menuDiv = useRef({});
  const toggleMenu = useRef();

  useEffect(() => {
    window.addEventListener("click", () => {
      if ((menuDiv.current.style.display = "block")) {
        menuDiv.current.style.display = "none";
      }
    });
    return () => {
      window.removeEventListener("click", () => {});
    };
  }, []);

  const handleClick = e => {
    e.stopPropagation();
    if (menuOpen === false) {
      menuDiv.current.style.display = "block";
      setMenuOpen(true);
    }
    if (menuOpen === true) {
      menuDiv.current.style.display = "none";
      setMenuOpen(false);
    }
  };

  return (
    <div>
      <div
        id="menu"
        ref={menuDiv}
        style={{
          display: "none"
        }}
      >Menu items</div>

      <div
        className="text-center"
        ref={toggleMenu}
        onClick={e => handleClick(e)}
      > Menu Button</div> 
    ) 
}
4 Answers

You are not actually removing the event listener from window when your component unmounts. The second argument to the removeEventListener should be a reference to the same function you added with addRemoveListener. E.g.

  useEffect(() => {
    const closeMenu = () => {
      if ((menuDiv.current.style.display = "block")) {
        menuDiv.current.style.display = "none";
      }
    };
    window.addEventListener("click", closeMenu);
    return () => {
      window.removeEventListener("click", closeMenu);
    };
  }, []);
    @Watch('openDropdown')
        openHandler(newValue) {
            newValue ? this.setOpen() : this.setClose();
        }
        componentWillLoad() {
            document.addEventListener('click', this.handleClick, false)
        }
        componentDidUpdate() {

            //
            if (this.dropdownNode != null && this.collapsableIconNode != null) {
                this.dropdownNode.style.top = this.collapsableIconNode.offsetTop + 20 + 'px'
                this.dropdownNode.style.left = this.collapsableIconNode.offsetLeft - 11 + 'px'
            }

        }
        componentDidUnload() {
            document.removeEventListener('click', this.handleClick, true)
        }
        handleClick = (e) => {
            if (this.collapsableIconNode.contains(e.target)) {
                this.openDropdown = true;
            }
            else {
                this.handleClickOutside()
            }

        }
        handleClickOutside() {
            this.openDropdown = false;
        }



    <span ref={collapsableIconNode => this.collapsableIconNode = collapsableIconNode as HTMLSpanElement} id="collapseIcon" class="collapsable-icon" onClick={() => this.setOpen()}>

This is Written in StencilJS, Logic is same,It is similar to React!!
const RightMenu = ({ t, history }) => {
let [menuOpen, setMenuOpen] = useState(false);


 useEffect(() => {
 window.addEventListener("click", () => {
 setMenuOpen(prevState => {
  return !prevState
 })
  });
return () => {
  window.removeEventListener("click", () => {});
};
}, []);

const handleClick = () => {
 e.stopPropagation();
 setMenuOpen(!menuOpen);
};

return (
<div>
  {menuOpen && (<div
    id="menu"

  >Menu items</div>)}

  <div
    className="text-center"

    onClick={handleClick}
  > Menu Button</div> 
) 

You do not need refs to achieve this, u can conditionally render the menu based on the menuOpen state like in the example provided.

Related