Implementing context menu using react hooks

Sometimes you want to override the browsers default context menu in your react app. You can easily do this with a simple custom react hook. Such custom hook should tell you the X and Y position of the context menu and boolean to say whether you should render the component.

Here is a simple implementation of such custom react hook

import { useEffect, useCallback, useState } from 'react';

const useContextMenu = (outerRef) => {
  const [xPos, setXPos] = useState('0px');
  const [yPos, setYPos] = useState('0px');
  const [menu, showMenu] = useState(false);

  const handleContextMenu = useCallback(
    (event) => {
      event.preventDefault();
      if (outerRef && outerRef.current.contains(event.target)) {
        setXPos(`${event.pageX}px`);
        setYPos(`${event.pageY}px`);
        showMenu(true);
      } else {
        showMenu(false);
      }
    },
    [showMenu, outerRef, setXPos, setYPos],
  );

  const handleClick = useCallback(() => {
    showMenu(false);
  }, [showMenu]);

  useEffect(() => {
    document.addEventListener('click', handleClick);
    document.addEventListener('contextmenu', handleContextMenu);
    return () => {
      document.removeEventListener('click', handleClick);
      document.removeEventListener('contextmenu', handleContextMenu);
    };
  }, []);

  return { xPos, yPos, menu };
};

export default useContextMenu;

The hook adds two event listener one to intercept the right click and other to intercept the click event.

  1. When you right click you can get X and Y position of the click using event.pageX and event.pageY
  2. When you left click you toggle the menu so that it gets hidden

Here is a Menu component that uses that hook

import React from 'react';

import useContextMenu from './useContextMenu';

const Menu = ({ outerRef }) => {
  const { xPos, yPos, menu } = useContextMenu(outerRef);

  if (menu) {
    return (
      <ul className="menu" style={{ top: yPos, left: xPos }}>
        <li>Item1</li>
        <li>Item2</li>
        <li>Item3</li>
      </ul>
    );
  }
  return <></>;
};

export default Menu;

You render the Menu component based on the boolean and you pass the X and Y position as inline styles.

Here is the demo of the custom hook and here is corresponding source code.

This post is also available on DEV.