💡

Context menu is only available in PRO version

ReactGrid comes with a custom context menu feature. Custom menu entries can be easily created thus allowing for personalized experiences. By default, the context menu is disabled. You can enable it by providing a handler function to your ReactGrid using the onContextMenu property.

Enabling simple context menu

This guide is based on handling data changes.

  1. Update imports by adding Id, SelectionMode and MenuOption interfaces
import { ReactGrid, CellChange, Row, Column, Id, MenuOption, SelectionMode } from "@silevis/reactgrid";
  1. Implement the context menu handler function

The menuOptions argument of simpleHandleContextMenu contains a list of default menu entries provided by ReactGrid - copy, cut and paste. For now, we'll simply define it, which will enable the default ReactGrid context menu.

const simpleHandleContextMenu = (
    selectedRowIds: Id[],
    selectedColIds: Id[],
    selectionMode: SelectionMode,
    menuOptions: MenuOption[]
  ): MenuOption[] => {
    return menuOptions;
};
 
return (
  <ReactGrid
    rows={rows}
    columns={columns}
    onCellsChanged={handleChanges}
    onContextMenu={simpleHandleContextMenu} // highlight-line
  />
);

Here's the result. Right-click anywhere in the grid to see the context menu.

ReactGrid

Code

const SimpleContextMenuHandlingSample = () => {
  
  const [columns] = React.useState<Column[]>(() => [
    { columnId: "Name", width: 100 },
    { columnId: "Surname", width: 100 }
  ]);
  
  const [rows, setRows] = React.useState<Row[]>(() => [
    {
      rowId: 0,
      cells: [
        { type: "header", text: "Name" },
        { type: "header", text: "Surname" }
      ]
    },
    {
      rowId: 1,
      cells: [
        { type: "text", text: "Thomas" },
        { type: "text", text: "Goldman" }
      ]
    },
    {
      rowId: 2,
      cells: [
        { type: "text", text: "Susie" },
        { type: "text", text: "Spencer" }
      ]
    },
    {
      rowId: 3,
      cells: [
        { type: "text", text: "" },
        { type: "text", text: "" }
      ]
    }
  ]);
  
  const handleChanges = (changes: CellChange[]) => {
    setRows((prevRows) => {
      changes.forEach((change) => {
        const changeRowIdx = prevRows.findIndex(
          (el) => el.rowId === change.rowId
        );
        const changeColumnIdx = columns.findIndex(
          (el) => el.columnId === change.columnId
        );
        prevRows[changeRowIdx].cells[changeColumnIdx] = change.newCell;
      });
      return [...prevRows];
    });
  };
  
  const simpleHandleContextMenu = (
    selectedRowIds: Id[],
    selectedColIds: Id[],
    selectionMode: SelectionMode,
    menuOptions: MenuOption[]
  ): MenuOption[] => {
    return menuOptions;
  };
  
  return (
    <ReactGrid
      rows={rows}
      columns={columns}
      onCellsChanged={handleChanges}
      onContextMenu={simpleHandleContextMenu}
    />
  );
}

render(<SimpleContextMenuHandlingSample/>)

ReactGrid

Preview

Advanced example - removing columns and rows

The default menu, though pretty, may seem a bit lacklustre when it comes to the functionality it provides. Let's try and extend it a bit - we'll make it possible to remove columns and rows using the context menu.

  1. Implement a more advanced context menu handler function
const handleContextMenu = (
  selectedRowIds: Id[],
  selectedColIds: Id[],
  selectionMode: SelectionMode,
  menuOptions: MenuOption[]
): MenuOption[] => {
  if (selectionMode === "row") {
    menuOptions = [
      ...menuOptions,
      {
        id: "removeRow",
        label: "Remove row",
        handler: () => setRows(rows.filter(row => !selectedRowIds.includes(row.rowId)))
      }
    ];
  }
  if (selectionMode === "column") {
    menuOptions = [
      ...menuOptions,
      {
        id: "removeColumn",
        label: "Remove column",
        handler: () => {
          const cols = columns.filter(column => !selectedColIds.includes(column.columnId));
          const columnsIdxs = columns
            .map((column, idx) => {
              if (!cols.includes(column)) return idx;
              return undefined;
            })
            .filter(idx => idx !== undefined);
          setRows(rows.map(row => ({
            ...row,
            cells: row.cells.filter((_, idx) => !columnsIdxs.includes(idx))
          })));
          setColumns(cols);
        }
      }
    ];
  }
  return menuOptions;
};
  1. Update the ReactGrid component's properties. Note that columns and rows must be selectable for our feature to work. After all, we will be removing the currently selected row or column.
return (
  <ReactGrid
    rows={rows}
    columns={columns}
    onCellsChanged={handleChanges} // highlight-line
    onContextMenu={handleContextMenu} // highlight-line
    enableColumnSelection // highlight-line
    enableRowSelection // highlight-line
  />
)

Live demo

You can see the effect of the changes we've just introduced in the demo below. To test the new feature, simply select a column or a row by clicking a header cell or one of the leftmost cells, then right-click anywhere and use the new menu entry.

ReactGrid

Code

const AdvancedContextMenuHandlingSample = () => {
  
  const [columns, setColumns] = React.useState<Column[]>(() => [
    { columnId: "Name", width: 100 },
    { columnId: "Surname", width: 100 }
  ]);
  
  const [rows, setRows] = React.useState<Row[]>(() => [
    {
      rowId: 0,
      cells: [
        { type: "header", text: "Name" },
        { type: "header", text: "Surname" }
      ]
    },
    {
      rowId: 1,
      cells: [
        { type: "text", text: "Thomas" },
        { type: "text", text: "Goldman" }
      ]
    },
    {
      rowId: 2,
      cells: [
        { type: "text", text: "Susie" },
        { type: "text", text: "Spencer" }
      ]
    },
    {
      rowId: 3,
      cells: [
        { type: "text", text: "" },
        { type: "text", text: "" }
      ]
    }
  ]);
  
  const handleChanges = (changes: CellChange[]) => {
    setRows((prevRows) => {
      changes.forEach((change) => {
        const changeRowIdx = prevRows.findIndex(
          (el) => el.rowId === change.rowId
        );
        const changeColumnIdx = columns.findIndex(
          (el) => el.columnId === change.columnId
        );
        prevRows[changeRowIdx].cells[changeColumnIdx] = change.newCell;
      });
      return [...prevRows];
    });
  };
  
  const handleContextMenu = (
    selectedRowIds: Id[],
    selectedColIds: Id[],
    selectionMode: SelectionMode,
    menuOptions: MenuOption[]
  ): MenuOption[] => {
    if (selectionMode === "row") {
      menuOptions = [
        ...menuOptions,
        {
          id: "removeRow",
          label: "Remove row",
          handler: () => setRows(rows.filter(row => !selectedRowIds.includes(row.rowId)))
        }
      ];
    }
    if (selectionMode === "column") {
      menuOptions = [
        ...menuOptions,
        {
          id: "removeColumn",
          label: "Remove column",
          handler: () => {
            const cols = columns.filter(column => !selectedColIds.includes(column.columnId));
            const columnsIdxs = columns
              .map((column, idx) => {
                if (!cols.includes(column)) return idx;
                return undefined;
              })
              .filter(idx => idx !== undefined);
            setRows(rows.map(row => ({
              ...row,
              cells: row.cells.filter((_, idx) => !columnsIdxs.includes(idx))
            })));
            setColumns(cols);
          }
        }
      ];
    }
    return menuOptions;
  };
  
  return (
    <ReactGrid
      rows={rows}
      columns={columns}
      onCellsChanged={handleChanges}
      onContextMenu={handleContextMenu}
      enableFillHandle
      enableRangeSelection
      enableColumnSelection
      enableRowSelection
    />
  );
  
}

render(<AdvancedContextMenuHandlingSample/>)

ReactGrid

Preview