1

I am working on a menu component. And with the help of the id, I made it possible for it to open and close on a click outside the component.

But I understand that I'm doing something wrong. If I open other menus, then the previous one does not close, I tried to pass props to override the id, but they are unstable (in fact, they stopped working altogether, but I didn’t dive further and look for an answer why)

I understand that this is not best practice. The component must be able to be reused. So I think how to improve it, should I use refs? Hook onChange? Which method do you think is more convenient, more practical?

import { useState } from "react";

function OverflowMenu(props) {
  const [isActive, setIsActive] = useState(false);
  const handleClick = (event) => {
    setIsActive((current) => !current);
    document.addEventListener("click", (event) => {
      const target = event.target;
      if (!target.closest(`#wrapper`) && !target.closest(`#menu`)) {
        setIsActive(false);
      }
    });
  };

  return (
    <>
      <div
        id={"wrapper"}
        onClick={handleClick}
        className="relative flex cursor-pointer"
      >
        {props.children} {props.content}
        <div
          id={"menu"}
          className={`${!isActive && "hidden"} 
          
          ${props.topRight && "-right-0 bottom-full"}
          ${props.topLeft && "-left-0 bottom-full"}
          ${props.bottomRight && "-right-0 top-full"}
          ${props.bottomLeft && "-left-0 top-full"}    
          
          absolute z-20 my-[4px] flex min-w-[112px] max-w-[280px] flex-col`}
        >
          <div className="scroll flex max-h-[310px] min-w-max flex-col overflow-hidden overflow-y-auto rounded-[8px] bg-blue-50 py-[8px] shadow-mm-1 dark:bg-gray-800 ">
            <>{props.menu}</>
          </div>
        </div>
      </div>
    </>
  );
}

export { OverflowMenu };

sllmn
  • 99
  • 8
  • 1
    If the goal is to detect outside click of the component, perhaps this [answer](https://stackoverflow.com/a/42234988/20436957) could help. – John Li Dec 20 '22 at 09:14

1 Answers1

1
Hi @sllmn,

There are a few ways you could improve the current implementation.

One option would be to use a ref hook. You could use that for passing the reference of HTML element.

const menuRef = useRef(null)

const handleClick = (event) => {
  setIsActive((current) => !current)
  if (!menuRef.current.contains(event.target)) {
    setIsActive(false)
  }
}
// code....
<div
  id={'menu'}
  ref={menuRef}
  className={""}>
  // code....
</div>

Another option. Which I found on internet, and it's a good one. To use useOnClickOutside hook. You will need to install it first.

yarn add react-use

Now you can use by importing that library.

import { useOnClickOutside } from 'react-use';

import { useState, useRef } from 'react';
import { useOnClickOutside } from 'react-use';

function OverflowMenu(props) {
  const [isActive, setIsActive] = useState(false);
  const menuRef = useRef(null);

  useOnClickOutside(menuRef, () => setIsActive(false));

  const handleClick = (event) => {
    setIsActive((current) => !current);
  };

  return (
    <>
      <div
        id={"wrapper"}
        onClick={handleClick}
        className="relative flex cursor-pointer"
      >
        {props.children} {props.content}
        <div
          id={"menu"}
          ref={menuRef}
          className={`${!isActive && "hidden"} 
          
          ${props.topRight && "-right-0 bottom-full"}
          ${props.topLeft && "-left-0 bottom-full"}
          ${props.bottomRight && "-right-0 top-full"}
          ${props.bottomLeft && "-left-0 top-full"}    
          
          absolute z-20 my-[4px] flex min-w-[112px] max-w-[280px] flex-col`}
        >
          <div className="scroll flex max-h-[310px] min-w-max flex-col overflow-hidden overflow-y-auto rounded-[8px] bg-blue-50 py-[8px] shadow-mm-1 dark:bg-gray-800 ">
            <>{props.menu}</>
          </div>
        </div>
      </div>
    </>
  );
}
export { OverflowMenu };
DSDmark
  • 1,045
  • 5
  • 11
  • 25
  • Thank you, I tried the first option, the menu works, but does not allow to close it when clicking outside the component. And in my case it is undesirable to install an additional dependency. – sllmn Dec 20 '22 at 10:02