DEV Community

William Baker
William Baker

Posted on

Advanced Context Menus with mantine-contextmenu in React

mantine-contextmenu is a powerful library for creating context menus (right-click menus) in React applications using Mantine UI components. It provides a flexible API for displaying contextual actions, submenus, and custom content when users right-click on elements. This guide walks through advanced usage of mantine-contextmenu with React and Mantine, including custom configurations, nested menus, and complex interaction patterns. This is part 45 of a series on using mantine-contextmenu with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Mantine UI installed and configured
  • Basic knowledge of React hooks (useState, useCallback)
  • Familiarity with Mantine UI components
  • Understanding of JavaScript/TypeScript

Installation

Install mantine-contextmenu and Mantine dependencies:

npm install mantine-contextmenu @mantine/core @mantine/hooks
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add mantine-contextmenu @mantine/core @mantine/hooks
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add mantine-contextmenu @mantine/core @mantine/hooks
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "mantine-contextmenu": "^1.0.0",
    "@mantine/core": "^6.0.0",
    "@mantine/hooks": "^6.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

Set up Mantine provider and context menu provider in your main entry file:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { MantineProvider } from '@mantine/core';
import { ContextMenuProvider } from 'mantine-contextmenu';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <MantineProvider>
      <ContextMenuProvider>
        <App />
      </ContextMenuProvider>
    </MantineProvider>
  </React.StrictMode>
  );
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple context menu. Create a new file src/ContextMenuExample.jsx:

// src/ContextMenuExample.jsx
import React from 'react';
import { useContextMenu } from 'mantine-contextmenu';
import { Box, Button } from '@mantine/core';

function ContextMenuExample() {
  const showContextMenu = useContextMenu();

  const handleRightClick = (event) => {
    showContextMenu({
      event,
      items: [
        {
          key: 'copy',
          label: 'Copy',
          onClick: () => {
            console.log('Copy clicked');
            alert('Copied!');
          }
        },
        {
          key: 'paste',
          label: 'Paste',
          onClick: () => {
            console.log('Paste clicked');
            alert('Pasted!');
          }
        },
        {
          key: 'delete',
          label: 'Delete',
          onClick: () => {
            console.log('Delete clicked');
            alert('Deleted!');
          },
          color: 'red'
        }
      ]
    });
  };

  return (
    <Box p="xl">
      <h2>Context Menu Example</h2>
      <Box
        onContextMenu={handleRightClick}
        p="md"
        style={{
          border: '2px dashed #ddd',
          borderRadius: '8px',
          cursor: 'context-menu',
          minHeight: '200px',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center'
        }}
      >
        Right-click here to open context menu
      </Box>
    </Box>
  );
}

export default ContextMenuExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import ContextMenuExample from './ContextMenuExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <ContextMenuExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Understanding the Basics

mantine-contextmenu provides several key features:

  • useContextMenu hook: Hook to access context menu functionality
  • showContextMenu function: Displays the context menu at the specified position
  • Menu items: Array of items with labels, icons, and actions
  • Submenus: Nested menu items for complex hierarchies
  • Custom content: Support for custom React components in menus
  • Mantine integration: Seamless integration with Mantine UI components

Key concepts for advanced usage:

  • Event handling: Use onContextMenu to capture right-click events
  • Menu configuration: Configure items, positioning, and behavior
  • Submenus: Create nested menu structures
  • Custom rendering: Use custom components for menu items
  • State management: Manage menu state and item states

Here's an example with submenus and icons:

// src/AdvancedContextMenuExample.jsx
import React from 'react';
import { useContextMenu } from 'mantine-contextmenu';
import { Box, Button } from '@mantine/core';
import { IconCopy, IconCut, IconPaste, IconTrash, IconEdit } from '@tabler/icons-react';

function AdvancedContextMenuExample() {
  const showContextMenu = useContextMenu();

  const handleRightClick = (event) => {
    showContextMenu({
      event,
      items: [
        {
          key: 'edit',
          label: 'Edit',
          icon: <IconEdit size={16} />,
          onClick: () => alert('Edit clicked')
        },
        {
          key: 'copy',
          label: 'Copy',
          icon: <IconCopy size={16} />,
          onClick: () => alert('Copy clicked')
        },
        {
          key: 'cut',
          label: 'Cut',
          icon: <IconCut size={16} />,
          onClick: () => alert('Cut clicked')
        },
        {
          key: 'paste',
          label: 'Paste',
          icon: <IconPaste size={16} />,
          onClick: () => alert('Paste clicked')
        },
        {
          key: 'divider',
          type: 'divider'
        },
        {
          key: 'delete',
          label: 'Delete',
          icon: <IconTrash size={16} />,
          color: 'red',
          onClick: () => alert('Delete clicked')
        },
        {
          key: 'more',
          label: 'More',
          children: [
            {
              key: 'rename',
              label: 'Rename',
              onClick: () => alert('Rename clicked')
            },
            {
              key: 'duplicate',
              label: 'Duplicate',
              onClick: () => alert('Duplicate clicked')
            }
          ]
        }
      ]
    });
  };

  return (
    <Box p="xl">
      <h2>Advanced Context Menu</h2>
      <Box
        onContextMenu={handleRightClick}
        p="md"
        style={{
          border: '2px dashed #ddd',
          borderRadius: '8px',
          cursor: 'context-menu',
          minHeight: '200px',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center'
        }}
      >
        Right-click here for advanced menu with submenus
      </Box>
    </Box>
  );
}

export default AdvancedContextMenuExample;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a file manager with context menus:

// src/FileManagerWithContextMenu.jsx
import React, { useState } from 'react';
import { useContextMenu } from 'mantine-contextmenu';
import { Box, Paper, Text, Group, ActionIcon } from '@mantine/core';
import { IconFolder, IconFile, IconEdit, IconTrash, IconCopy, IconCut, IconPaste } from '@tabler/icons-react';

function FileManagerWithContextMenu() {
  const showContextMenu = useContextMenu();
  const [files, setFiles] = useState([
    { id: 1, name: 'Documents', type: 'folder' },
    { id: 2, name: 'Images', type: 'folder' },
    { id: 3, name: 'report.pdf', type: 'file' },
    { id: 4, name: 'photo.jpg', type: 'file' },
    { id: 5, name: 'notes.txt', type: 'file' }
  ]);
  const [selectedFile, setSelectedFile] = useState(null);

  const handleFileRightClick = (event, file) => {
    event.preventDefault();
    setSelectedFile(file);

    const menuItems = [
      {
        key: 'open',
        label: 'Open',
        onClick: () => {
          alert(`Opening ${file.name}`);
        }
      },
      {
        key: 'edit',
        label: 'Edit',
        icon: <IconEdit size={16} />,
        onClick: () => {
          alert(`Editing ${file.name}`);
        }
      },
      {
        key: 'divider1',
        type: 'divider'
      },
      {
        key: 'copy',
        label: 'Copy',
        icon: <IconCopy size={16} />,
        onClick: () => {
          alert(`Copied ${file.name}`);
        }
      },
      {
        key: 'cut',
        label: 'Cut',
        icon: <IconCut size={16} />,
        onClick: () => {
          alert(`Cut ${file.name}`);
        }
      },
      {
        key: 'paste',
        label: 'Paste',
        icon: <IconPaste size={16} />,
        disabled: true,
        onClick: () => {
          alert('Paste clicked');
        }
      },
      {
        key: 'divider2',
        type: 'divider'
      },
      {
        key: 'rename',
        label: 'Rename',
        onClick: () => {
          const newName = prompt('Enter new name:', file.name);
          if (newName) {
            setFiles(files.map(f => 
              f.id === file.id ? { ...f, name: newName } : f
            ));
          }
        }
      },
      {
        key: 'delete',
        label: 'Delete',
        icon: <IconTrash size={16} />,
        color: 'red',
        onClick: () => {
          if (confirm(`Delete ${file.name}?`)) {
            setFiles(files.filter(f => f.id !== file.id));
          }
        }
      }
    ];

    showContextMenu({
      event,
      items: menuItems
    });
  };

  return (
    <Box p="xl">
      <h2>File Manager with Context Menu</h2>
      <Box mt="md">
        {files.map(file => (
          <Paper
            key={file.id}
            p="md"
            mb="sm"
            onContextMenu={(e) => handleFileRightClick(e, file)}
            style={{
              cursor: 'context-menu',
              border: selectedFile?.id === file.id ? '2px solid #007bff' : '1px solid #ddd',
              borderRadius: '8px'
            }}
          >
            <Group>
              {file.type === 'folder' ? (
                <IconFolder size={24} color="#ffc107" />
              ) : (
                <IconFile size={24} color="#007bff" />
              )}
              <Text>{file.name}</Text>
            </Group>
          </Paper>
        ))}
      </Box>
      <Text size="sm" color="dimmed" mt="md">
        Right-click on any file or folder to see the context menu
      </Text>
    </Box>
  );
}

export default FileManagerWithContextMenu;
Enter fullscreen mode Exit fullscreen mode

Now create a table with row context menus:

// src/TableWithContextMenu.jsx
import React, { useState } from 'react';
import { useContextMenu } from 'mantine-contextmenu';
import { Box, Table, Text, Badge } from '@mantine/core';
import { IconEdit, IconTrash, IconEye, IconDownload } from '@tabler/icons-react';

function TableWithContextMenu() {
  const showContextMenu = useContextMenu();
  const [users, setUsers] = useState([
    { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
    { id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'User' }
  ]);

  const handleRowRightClick = (event, user) => {
    showContextMenu({
      event,
      items: [
        {
          key: 'view',
          label: 'View Details',
          icon: <IconEye size={16} />,
          onClick: () => {
            alert(`Viewing ${user.name}`);
          }
        },
        {
          key: 'edit',
          label: 'Edit User',
          icon: <IconEdit size={16} />,
          onClick: () => {
            alert(`Editing ${user.name}`);
          }
        },
        {
          key: 'download',
          label: 'Download',
          icon: <IconDownload size={16} />,
          onClick: () => {
            alert(`Downloading ${user.name}'s data`);
          }
        },
        {
          key: 'divider',
          type: 'divider'
        },
        {
          key: 'delete',
          label: 'Delete User',
          icon: <IconTrash size={16} />,
          color: 'red',
          onClick: () => {
            if (confirm(`Delete ${user.name}?`)) {
              setUsers(users.filter(u => u.id !== user.id));
            }
          }
        }
      ]
    });
  };

  return (
    <Box p="xl">
      <h2>Table with Context Menu</h2>
      <Table mt="md" striped highlightOnHover>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
          </tr>
        </thead>
        <tbody>
          {users.map(user => (
            <tr
              key={user.id}
              onContextMenu={(e) => handleRowRightClick(e, user)}
              style={{ cursor: 'context-menu' }}
            >
              <td>{user.name}</td>
              <td>{user.email}</td>
              <td>
                <Badge color={user.role === 'Admin' ? 'red' : 'blue'}>
                  {user.role}
                </Badge>
              </td>
            </tr>
          ))}
        </tbody>
      </Table>
      <Text size="sm" color="dimmed" mt="md">
        Right-click on any row to see the context menu
      </Text>
    </Box>
  );
}

export default TableWithContextMenu;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import FileManagerWithContextMenu from './FileManagerWithContextMenu';
import TableWithContextMenu from './TableWithContextMenu';
import './App.css';

function App() {
  return (
    <div className="App">
      <FileManagerWithContextMenu />
      <TableWithContextMenu />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • File manager with context menus
  • Table rows with context menus
  • Menu items with icons
  • Submenus and dividers
  • Dynamic menu items based on context
  • State management for selected items

Common Issues / Troubleshooting

  1. Context menu not appearing: Ensure ContextMenuProvider wraps your app and MantineProvider is set up correctly. Also make sure you're calling showContextMenu with a valid event object.

  2. Menu positioning issues: The menu automatically positions itself based on the event coordinates. If it appears in the wrong place, check that you're passing the correct event object from onContextMenu.

  3. Menu items not working: Ensure onClick handlers are properly defined. Check for JavaScript errors in the console that might prevent handlers from executing.

  4. Submenus not displaying: Make sure submenu items are defined in the children property of a menu item. The structure should be: { key: 'parent', label: 'Parent', children: [...] }.

  5. Styling conflicts: If the menu doesn't look right, check for CSS conflicts with Mantine styles. You can customize menu appearance using Mantine's theming system.

  6. Event propagation: If the context menu appears but immediately disappears, you might need to call event.preventDefault() in your onContextMenu handler to prevent default browser behavior.

Next Steps

Now that you have an advanced understanding of mantine-contextmenu:

  • Explore advanced menu configurations and theming
  • Learn about custom menu item rendering
  • Implement keyboard navigation for accessibility
  • Add menu item states (disabled, loading, etc.)
  • Integrate with Mantine's notification system
  • Learn about other context menu libraries
  • Check the official repository: https://github.com/icflorescu/mantine-contextmenu
  • Look for part 46 of this series for more advanced topics

Summary

You've successfully integrated mantine-contextmenu into your React application with advanced features including file manager context menus, table row menus, submenus, and custom menu items. mantine-contextmenu provides a powerful, Mantine-integrated solution for creating context menus with extensive customization options.

SEO Keywords

mantine-contextmenu
React context menu
mantine-contextmenu tutorial
React right-click menu
mantine-contextmenu installation
React Mantine context menu
mantine-contextmenu example
React contextual menu
mantine-contextmenu setup
React menu library
mantine-contextmenu customization
React context menu hooks
mantine-contextmenu submenus
React Mantine UI
mantine-contextmenu getting started

Top comments (0)