user6377312
user6377312

Reputation:

React - what is wrong with my custom hook for click outside of element?

I have a custom hook for click outside of element in a menu. What happens is: when i click on "open menu", it opens menu, when i click outside it closes it (all good) but when I click again on the button "open menu" to close it (after opening it), it closes it for a second and then opens it again. I have been looking trying ti find the reason but cant. any help would be great. Sorry if i didnt phrase my question well, i did not know how to ask more clear :) Thank you!!

const { useState, useRef } = React;

const App = () => {
  const [showMenu, setShowMenu] = React.useState(false)

  return (
    <div>
      <div className="menu-section-conatainer">
        <button
          className={"menu-link"}
          onClick={() => {
            setShowMenu(true)
          }}
          name={title}
        >
          open menu
        </button>
      </div>

      <Menu
        showState={showMenu}
        clickHandler={setShowMenu}
      >
        {children}
      </Menu>
    </div>
  )
}

const Menu = ({ children, clickHandler, showState}) => {
  const ref = useRef()
  useOnClickOutside(ref, () => clickHandler(false))

  return (
    <div
      className="menu-expanded-wrapper"
    >
      <div ref={ref} className="menu-expanded">
        {children}
      </div>
    </div>
  )
}

const useOnClickOutside = (ref, handler) => {
  React.useEffect(() => {
    const listener = event => {
      if (!ref.current || ref.current.contains(event.target)) {
        return
      }
      handler(event)
    }

    document.addEventListener("mousedown", listener)
    document.addEventListener("touchstart", listener)

    return () => {
      document.removeEventListener("mousedown", listener)
      document.removeEventListener("touchstart", listener)
    }
  }, [ref, handler])
}

Upvotes: 3

Views: 2841

Answers (1)

Henry Woody
Henry Woody

Reputation: 15662

The reason your menu component closes then opens again immediately when clicking the button while the menu is already open is that when you click the button, the listener in useOnClickOutside is called (and closes the menu) then the onClick function on the button is called (and opens the menu).

You can fix this by moving the use of useOnClickOutside to the App component and placing the ref on an element that's a parent of both the button and the menu so that clicks on the button are ignored for the purposes of "outside". Additionally, this is probably a better structure anyway so that the opening/closing logic is housed solely in the App component.

Then you should update your button's onClick function so that it toggles showMenu rather than just setting its value to true. You can do this simply with setShowMenu(prev => !prev).

Here's an example:

const App = () => {
  const [showMenu, setShowMenu] = React.useState(false)
  const ref = React.useRef()
  useOnClickOutside(ref, () => setShowMenu(false))

  return (
    <div ref={ref}>
      <div className="menu-section-conatainer">
        <button
          className={"menu-link"}
          onClick={() => {
            setShowMenu(prev => !prev)
          }}
          name="Toggle Menu"
        >
          {showMenu ? "Hide Menu" : "Show Menu"}
        </button>
      </div>

      <Menu showState={showMenu}>
        Menu Items
      </Menu>
    </div>
  )
}

const Menu = ({ children, showState}) => {
  return (
    <div className="menu-expanded-wrapper">
      <div className={ showState ? "menu-expanded" : "menu-collapsed" }>
        {children}
      </div>
    </div>
  )
}

With no changes to your useOnClickOutside hook.

Upvotes: 8

Related Questions