1

I tried adding the condition on mouseenter and mouseleave however the modal is not working but when I tried to create a button onClick={() => {openModal();}} the modal will show up. Can you please tell me what's wrong on my code and which part.

const openModal = event => {
    if (event) event.preventDefault();
    setShowModal(true);
  };

  const closeModal = event => {
    if (event) event.preventDefault();
    setShowModal(false);
  };

  function useHover() {
    const ref = useRef();
    const [hovered, setHovered] = useState(false);
    const enter = () => setHovered(true);
    const leave = () => setHovered(false);

    useEffect(() => {
      if (ref.current.addEventListener('mouseenter', enter)) {
        openModal();
      } else if (ref.current.addEventListener('mouseleave', leave)) {
        closeModal();
      }
      return () => {
        if (ref.current.addEventListener('mouseenter', enter)) {
          openModal();
        } else if (ref.current.addEventListener('mouseleave', leave)) {
          closeModal();
        }
      };
    }, [ref]);

    return [ref, hovered];
  }

  const [ref, hovered] = useHover();



<div className="hover-me" ref={ref}>hover me</div>

  {hovered && (
    <Modal active={showModal} closeModal={closeModal} className="dropzone-modal">
      <div>content here</div>
    </Modal>
  )}
cachess
  • 163
  • 1
  • 5
  • 18

2 Answers2

5

building on Drew Reese's answer, you can cache the node reference inside the useEffect closure itself, and it simplifies things a bit. You can read more about closures in this stackoverflow thread.

const useHover = () => {
  const ref = useRef();
  const [hovered, setHovered] = useState(false);
  const enter = () => setHovered(true);
  const leave = () => setHovered(false);

  useEffect(() => {
    const el = ref.current; // cache external ref value for cleanup use
    if (el) {
      el.addEventListener("mouseenter", enter);
      el.addEventListener("mouseleave", leave);

      return () => {
        el.removeEventLisener("mouseenter", enter);
        el.removeEventLisener("mouseleave", leave);
      };
    }
  }, []);

  return [ref, hovered];
};
woojoo666
  • 7,801
  • 7
  • 45
  • 57
2

I almost gave up and passed on this but it was an interesting problem.

Issues:

  • The first main issue is with the useEffect hook of your useHover hook, it needs to add/remove both event listeners at the same time, when the ref's current component mounts and unmounts. The key part is the hook needs to cache the current ref within the effect hook in order for the cleanup function to correctly function.

  • The second issue is you aren't removing the listener in the returned effect hook cleanup function.

  • The third issue is that EventTarget.addEventListener() returns undefined, which is a falsey value, thus your hook never calls modalOpen or modalClose

  • The last issue is with the modal open/close state/callbacks being coupled to the useHover hook's implementation. (this is fine, but with this level of coupling you may as well just put the hook logic directly in the parent component, completely defeating the point of factoring it out into a reusable hook!)

Solution

Here's what I was able to get working:

const useHover = () => {
  const ref = useRef();
  const _ref = useRef();
  const [hovered, setHovered] = useState(false);
  const enter = () => setHovered(true);
  const leave = () => setHovered(false);

  useEffect(() => {
    if (ref.current) {
      _ref.current = ref.current; // cache external ref value for cleanup use
      ref.current.addEventListener("mouseenter", enter);
      ref.current.addEventListener("mouseleave", leave);
    }

    return () => {
      if (_ref.current) {
        _ref.current.removeEventLisener("mouseenter", enter);
        _ref.current.removeEventLisener("mouseleave", leave);
      }
    };
  }, []);

  return [ref, hovered];
};

Edit awesome-bardeen-cnqjs

Note: using this with a modal appears to have interaction issues as I suspected, but perhaps your modal works better.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • you can probably cache the current ref inside the useEffect closure. If I could write the code in a comment I would but I put it in [an answer instead](https://stackoverflow.com/a/66378678/1852456) – woojoo666 Feb 26 '21 at 01:05