Properly Handling Right-Click (Context Menu) Events in React
A common oversight in React applications is not properly handling right-click (contextmenu
) events, which can lead to broken user experiences or security issues. Here’s how to handle them correctly:
Basic Right-Click Handling
function RightClickExample() {
const handleContextMenu = (e) => {
e.preventDefault(); // Prevent default browser context menu
console.log('Right-click at:', e.clientX, e.clientY);
// Your custom context menu logic here
};
return (
<div onContextMenu={handleContextMenu}>
Right-click me for custom menu
</div>
);
}
Common Mistakes to Avoid
- Forgetting to prevent default behavior:
// ❌ Browser menu will still appear
const handleContextMenu = (e) => {
console.log('Right-clicked');
};
- Not cleaning up global listeners:
// ❌ Potential memory leak
useEffect(() => {
window.addEventListener('contextmenu', handleGlobalRightClick);
}, []);
- Ignoring touch events on mobile:
// ❌ Won't work for touch-and-hold on mobile
<div onContextMenu={handleRightClick}>
Complete Solution
1. Custom Context Menu Component
function CustomContextMenu() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [showMenu, setShowMenu] = useState(false);
const handleContextMenu = (e) => {
e.preventDefault();
setPosition({ x: e.clientX, y: e.clientY });
setShowMenu(true);
};
const handleClick = () => {
setShowMenu(false);
};
useEffect(() => {
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, []);
return (
<div onContextMenu={handleContextMenu}>
Right-click here
{showMenu && (
<div
className="context-menu"
style={{ left: position.x, top: position.y }}
onClick={(e) => e.stopPropagation()}
>
<button>Option 1</button>
<button>Option 2</button>
</div>
)}
</div>
);
}
2. Mobile-Friendly Solution
function TouchFriendlyMenu() {
const [longPress, setLongPress] = useState(false);
const timerRef = useRef();
const startPressTimer = () => {
timerRef.current = setTimeout(() => {
setLongPress(true);
}, 500); // 500ms threshold for long press
};
const handleTouchEnd = () => {
clearTimeout(timerRef.current);
};
const handleTouchMove = () => {
clearTimeout(timerRef.current);
};
return (
<div
onTouchStart={startPressTimer}
onTouchEnd={handleTouchEnd}
onTouchMove={handleTouchMove}
onContextMenu={(e) => {
e.preventDefault();
setLongPress(true);
}}
>
{longPress && (
<div className="mobile-menu">
<button onClick={() => setLongPress(false)}>Close</button>
</div>
)}
</div>
);
}
Advanced Considerations
- Keyboard Accessibility:
// Add keyboard shortcut (Shift+F10 typically opens context menu)
useEffect(() => {
const handleKeyDown = (e) => {
if (e.shiftKey && e.key === 'F10') {
e.preventDefault();
// Show menu at current focus position
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
- Nested Context Menus:
function handleNestedContextMenu(e, level) {
e.preventDefault();
e.stopPropagation(); // Prevent parent menus from opening
console.log(`Level ${level} menu opened`);
}
- Performance Optimization:
// Memoize handler if needed
const handleContextMenu = useCallback((e) => {
e.preventDefault();
// Menu logic
}, []);
Security Considerations
- Prevent default carefully – Some security tools monitor disabled context menus
- Don’t block all right-clicks – Allow access to browser features when appropriate
- Consider user expectations – Custom menus should provide clear value
Best Practices
- Always provide an alternative way to access functionality
- Make custom menus accessible via keyboard
- Style menus to clearly distinguish from page content
- Test on both desktop and mobile devices
- Consider using libraries like
react-contextmenu
for production apps