Build a context menu with React and TypeScript
6 min read
Creating your own right-click context menu in React is easy. Using your own context menu means you'll need one less npm package to install in your project, making it more lightweight. Building a custom context menu with React is also a good front-end programming exercise for practice.
What is a context menu?
A context menu is also called a right-click menu. It's a menu that appears when a user right-clicks on the screen. It offers a set of options that are available to the user in the current context of the application to which it belongs. That's why it's called a context menu.
Right-clicking within a browser tab will show your operating system's native context menu. One context menu that we are all familiar with is the one that shows up when we right-click after copying text. This context menu provides us with options such as Cut, Copy, and Paste.
A context menu hook
Let's start by building a custom useContextMenu
hook that we'll store in /hooks/useContextMenu.ts
.
This hook will be responsible for implementing all the logic required for a context menu. This means that the context menu component that we'll build later can have the single responsibility of displaying the context menu. It won't have to worry about implementing any logic.
The useContextMenu
hook will:
- Register and de-register event listeners.
- Keep track of the right-click mouse position.
- Keep track of whether the context menu is shown or hidden.
We'll listen to the contextmenu
event to capture right-clicks. We'll also to listen to click
events to know when to close our context menu.
import { useEffect, useCallback, useState } from 'react';
type AnchorPoint = {
x: number;
y: number;
};
const useContextMenu = () => {
const [anchorPoint, setAnchorPoint] = useState<AnchorPoint>({ x: 0, y: 0 });
const [isShown, setIsShow] = useState(false);
const handleContextMenu = useCallback(
(event: MouseEvent) => {
event.preventDefault();
setAnchorPoint({ x: event.pageX, y: event.pageY });
setIsShow(true);
},
[setIsShow, setAnchorPoint]
);
const handleClick = useCallback(() => {
if (isShown) {
setIsShow(false);
}
}, [isShown]);
useEffect(() => {
document.addEventListener('click', handleClick);
document.addEventListener('contextmenu', handleContextMenu);
return () => {
document.removeEventListener('click', handleClick);
document.removeEventListener('contextmenu', handleContextMenu);
};
});
return { anchorPoint, isShown };
};
export { useContextMenu };
In this custom hook, we added the event listeners we needed in a useEffect
hook and de-registered them in the useEffect
cleanup function.
The handleContextMenu
method is called when a user right-clicks anywhere on the screen. It's responsible for saving the right-click anchor point of the user so that we know where exactly we need to display the context menu. This method is also responsible for toggling the isShown
status of the context menu to true
to make it visible.
The handleClick
method is called when a user left-clicks anywhere on the screen. It's responsible for closing the context menu. If the isShown
status of the context menu is set to true
because it's visible, then this method will set it to false
to hide it.
A context menu component
Let's create a reusable component for our context menu and store it in /components/ContextMenu.tsx
.
This component will make use of the useContextMenu
hook that we just created in order to retrieve the anchorPoint
and the isShown
status.
import * as React from 'react';
import { useContextMenu } from '../../hooks/useContextMenu';
import styles from './ContextMenu.module.css';
type Props = {
items: Array<string>;
onClick: (item: string) => void;
};
const ContextMenu = ({ items }: Props) => {
const { anchorPoint, isShown } = useContextMenu();
if (!isShown) {
return null;
}
return (
<ul
className={styles.ContextMenu}
style={{ top: anchorPoint.y, left: anchorPoint.x }}
>
{items.map((item) => (
<li key={item} onClick={() => onClick(item)}>{item}</li>
))}
</ul>
);
};
export { ContextMenu };
If the context menu is not being shown, then the ContextMenu
component simply returns null
. However, if the context menu is being shown, the ContextMenu
component renders it at the precise location provided by the coordinates of the anchorPoint
.
The ContextMenu
component receives a list configurable menu items, making it a reusable component for many different types of context menus.
Clicking on a context menu item triggers an onClick
event on the ContextMenu
component. This event passes along the name of the item that was clicked.
Styling the context menu
The next step is to add the CSS styles for the ContextMenu
component. To do so, we'll make use of CSS Modules and create a ContextMenu.module.css
file.
If you recall, we've already imported this CSS file in the ContextMenu
component using the following import.
import styles from './ContextMenu.module.css';
Let's make use of the .ContextMenu
CSS class to add styling to the context menu. This class was assigned to the <ul>
element in the ContextMenu
component via className={styles.ContextMenu}
. It's important that we use absolute positioning for this CSS class.
Also, let's use the not(:last-child)
CSS rule on the <li>
element to add bottom margins on all context menu items, except for the last one.
.ContextMenu {
position: absolute;
background: #eee;
border: 1px solid #ccc;
border-radius: 0.3rem;
padding: 1rem;
width: 8rem;
list-style: none;
margin: 0;
}
.ContextMenu li {
cursor: pointer;
}
.ContextMenu li:not(:last-child) {
margin: 0 0 1rem;
}
Making use of the context menu
Let's complete this exercise by making use of the ContextMenu
component in our App
component. This will allow us to test the context menu that we've built.
We'll pass a list of menu items to our ContextMenu
component to populate the context menu. We'll also provide a function to the onClick
handler of the ContextMenu
component so that we can be notified when a context menu item is clicked.
import * as React from 'react';
import { ContextMenu } from './components';
export default function App() {
const items = ['Cut', 'Copy', 'Paste'];
const onClick = (item: string) => {
alert(`${item} was clicked.`);
};
return (
<div>
<h1>React Context Menu</h1>
<p>Right-click anywhere to see the context menu</p>
<p>Left-click to close the context menu</p>
<ContextMenu items={items} onClick={onClick} />
</div>
);
}
Right-clicking anywhere on the page now shows a context menu with the menu items that we provided. Once the context menu is shown, left-clicking anywhere on the page closes it.
React context menu demo
Conclusion
In this article, we learned how to build our own context menu with a React custom hook and a reusable React component.
You can now implement this context menu in your next application or in a shared component library that you are building.