Контекстное меню в React: лучший подход
Введение
Контекстное меню — это всплывающее меню, которое появляется при нажатии правой кнопки мыши. В браузерах по умолчанию открывается стандартное меню, но в веб-приложениях часто нужно создавать свое, кастомное. В этой статье разберем, как это сделать в React.
Почему нельзя просто использовать onContextMenu
?
Метод onContextMenu
позволяет отловить правый клик, но у него есть несколько проблем:
- Ограниченное управление позиционированием — сложно точно задать место появления меню.
- Меню может выйти за границы экрана — пользователь не увидит часть пунктов.
- Ограниченная кастомизация — сложно изменить стиль и добавить новые функции.
Чтобы решить эти проблемы, создадим своё контекстное меню с полным контролем над его поведением.
Шаг 1: Создаем хук useContextMenu
Начнем с создания хука, который управляет состоянием меню: его видимостью, позицией и содержимым.
import { useState, useCallback } from "react";
export interface ContextMenuHandlers {
open: (x: number, y: number, record: any) => void;
close: () => void;
}
export function useContextMenu(handlerRef: React.RefObject<ContextMenuHandlers>) {
// Состояние меню: открыто или нет, координаты и данные записи
const [state, setState] = useState({
visible: false,
x: 0,
y: 0,
record: null,
});
// Функция открытия меню
const open = (x: number, y: number, record: any) => {
const menuWidth = 150; // Ширина меню
const menuHeight = 100; // Высота меню
const maxX = window.innerWidth - menuWidth;
const maxY = window.innerHeight - menuHeight;
// Устанавливаем новое состояние
setState({
visible: true,
x: Math.min(x, maxX), // Ограничиваем координаты
y: Math.min(y, maxY),
record,
});
};
// Функция закрытия меню
const close = () => {
setState((prev) => ({ ...prev, visible: false }));
};
// Связываем методы с handlerRef, если он передан
if (handlerRef.current) {
handlerRef.current.open = open;
handlerRef.current.close = close;
}
return state;
}
Как работает этот хук?
- Используем
useState
, чтобы хранить:visible
— отображается ли менюx
иy
— координаты менюrecord
— данные записи, на которой был клик
- Функция
open()
- Принимает координаты клика и данные записи
- Вычисляет, не выходит ли меню за границы экрана
- Обновляет состояние меню
- Функция
close()
- Просто скрывает меню
- Если передан
handlerRef
, то записываем в него функцииopen
иclose
, чтобы их можно было вызвать извне
Шаг 2: Создаем компонент ContextMenu
Теперь напишем сам компонент меню:
import React from "react";
import "./ContextMenu.scss";
import { ContextMenuHandlers, useContextMenu } from "../../hooks/useContextMenu";
interface ContextMenuProps {
handlerRef: React.RefObject<ContextMenuHandlers>;
onOpenNewTab: (record: any) => void;
}
export default function ContextMenu({ handlerRef, onOpenNewTab }: ContextMenuProps) {
const { visible, x, y, record } = useContextMenu(handlerRef);
// Функция для открытия записи в новой вкладке
const handleNewTab = () => {
if (record) {
onOpenNewTab(record);
}
};
if (!visible) return null; // Если меню скрыто, ничего не рендерим
return (
<div
className="context-menu"
style={{ top: y, left: x }}
onMouseLeave={() => handlerRef.current?.close()}
>
<div className="context-menu-item" onClick={handleNewTab}>
Открыть в новой вкладке
</div>
<div className="context-menu-item" onClick={() => handlerRef.current?.close()}>
Отмена
</div>
</div>
);
}
Что здесь происходит?
- Получаем данные о меню из хука
useContextMenu
- Определяем функцию
handleNewTab()
, которая открывает запись в новой вкладке - Если меню скрыто (
!visible
), то ничего не отображаем - Рендерим контейнер меню и два пункта:
- "Открыть в новой вкладке"
- "Отмена"
- Если курсор уходит за пределы меню (
onMouseLeave
), меню автоматически закрывается

Шаг 3: Добавляем обработчик handleContextMenu
Теперь нужно подключить наше контекстное меню к списку данных. Добавим обработчик:
const handleContextMenu = (event: React.MouseEvent, record: any) => {
event.preventDefault(); // Отменяем стандартное меню браузера
contextMenuRef.current?.open(event.clientX, event.clientY, record);
};
Как это работает?
event.preventDefault()
— отключает стандартное контекстное менюcontextMenuRef.current?.open()
— открывает наше меню в нужном месте
Готовые библиотеки
Если не хочется писать код с нуля, можно использовать готовые решения:
@szhsin/react-menu
— мощный UI-компонент с анимацией.radix-ui/menu
— доступный и кастомизируемый вариант.
UI-советы
- Не давайте меню выходить за границы экрана — ограничьте координаты.
- Добавляйте иконки для пунктов — пользователи быстрее ориентируются.
- Используйте анимации появления — меню будет выглядеть плавнее.
- Продумайте логику закрытия — при клике вне меню или на
Esc
оно должно скрываться.
Итог
Мы создали удобное контекстное меню с учетом границ экрана и интеграцией в компонент с данными. Также рассмотрели альтернативные библиотеки и лучшие UX-практики. Теперь вы можете легко внедрить контекстное меню в свой React-проект!