Introduction

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 [people, setPeople] = React.useState<Person[]>(getPeople());
  const rows = getRows(people);
  const columns = getColumns();
 
  const handleChanges = (changes: CellChange[]) => {
    setPeople((prevPeople) => applyChangesToPeople(changes, prevPeople));
  };
 
  const simpleHandleContextMenu = (
    selectedRowIds: Id[],
    selectedColIds: Id[],
    selectionMode: SelectionMode,
    menuOptions: MenuOption[]
  ): MenuOption[] => {
    return menuOptions;
  }
 
  return (
    <ReactGrid
      rows={rows}
      columns={columns}
      onCellsChanged={handleChanges}
      onContextMenu={simpleHandleContextMenu}
    />
  );

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

ReactGrid

Code

interface Person {
    name: string;
    surname: string;
}

const getPeople = (): Person[] => [
  { name: "Thomas", surname: "Goldman" },
  { name: "Susie", surname: "Quattro" },
  { name: "", surname: "" }
];

const getColumns = (): Column[] => [
  { columnId: "name", width: 150 },
  { columnId: "surname", width: 150 }
];

const headerRow: Row = {
  rowId: "header",
  cells: [
    { type: "header", text: "Name" },
    { type: "header", text: "Surname" }
  ]
};

const getRows = (people: Person[]): Row[] => [
  headerRow,
  ...people.map<Row>((person, idx) => ({
    rowId: idx,
    cells: [
      { type: "text", text: person.name },
      { type: "text", text: person.surname }
    ]
  }))
];

const applyChangesToPeople = (
  changes: CellChange[],
  prevPeople: Person[]
): Person[] => {
  changes.forEach((change) => {
    if (change.newCell.type === 'text') {
      const personIndex = change.rowId;
      const fieldName = change.columnId;
      prevPeople[personIndex][fieldName] = change.newCell.text;
    }
  });
  return [...prevPeople];
};

const SimpleContextMenuHandlingSample = () => {
  const [people, setPeople] = React.useState<Person[]>(getPeople());
  
  const rows = getRows(people);
  const columns = getColumns();
  
  const handleChanges = (changes: CellChange[]) => {
    setPeople((prevPeople) => applyChangesToPeople(changes, prevPeople));
  };
  
  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 people 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: "removePerson",
        label: "Remove person",
        handler: () => {
          setPeople(prevPeople => {
            return [...prevPeople.filter((person, idx) => !selectedRowIds.includes(idx))]
          })
        }
      }
    ];
  }
  return menuOptions;
}
  1. Update the ReactGrid component's properties. Note that rows must be selectable for our feature to work. After all, we will be removing the currently selected rows.
return (
  <ReactGrid
    rows={rows}
    columns={columns}
    onCellsChanged={handleChanges}
    onContextMenu={handleContextMenu}
    enableFillHandle
    enableRangeSelection
    enableRowSelection
  />
)

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 row by clicking a header cell or one of the leftmost cells, then right-click anywhere and use the new menu entry.

ReactGrid

Code

interface Person {
  name: string;
  surname: string;
}

const getPeople = (): Person[] => [
  { name: "Thomas", surname: "Goldman" },
  { name: "Susie", surname: "Quattro" },
  { name: "", surname: "" }
];

const getColumns = (): Column[] => [
  { columnId: "name", width: 150 },
  { columnId: "surname", width: 150 }
];

const headerRow: Row = {
  rowId: "header",
  cells: [
    { type: "header", text: "Name" },
    { type: "header", text: "Surname" }
  ]
};

const getRows = (people: Person[]): Row[] => [
  headerRow,
  ...people.map<Row>((person, idx) => ({
    rowId: idx,
    cells: [
      { type: "text", text: person.name },
      { type: "text", text: person.surname }
    ]
  }))
];

const applyChangesToPeople = (
  changes: CellChange[],
  prevPeople: Person[]
): Person[] => {
  changes.forEach((change) => {
    if (change.newCell.type === 'text') {
      const personIndex = change.rowId;
      const fieldName = change.columnId;
      prevPeople[personIndex][fieldName] = change.newCell.text;
    }
  });
  return [...prevPeople];
};

const AdvancedContextMenuHandlingSample = () => {
  const [people, setPeople] = React.useState<Person[]>(getPeople());
  const [columns] = React.useState<Column[]>(getColumns());
  
  const rows = getRows(people);
  
  const handleChanges = (changes: CellChange[]) => {
    setPeople((prevPeople) => applyChangesToPeople(changes, prevPeople));
  };
  
  const handleContextMenu = (
    selectedRowIds: Id[],
    selectedColIds: Id[],
    selectionMode: SelectionMode,
    menuOptions: MenuOption[]
  ): MenuOption[] => {
    if (selectionMode === "row") {
      menuOptions = [
        ...menuOptions,
        {
          id: "removePerson",
          label: "Remove person",
          handler: () => {
            setPeople(prevPeople => {
              return [...prevPeople.filter((person, idx) => !selectedRowIds.includes(idx))]
            })
          }
        }
      ];
    }
    return menuOptions;
  }
  
  return (
    <ReactGrid
      rows={rows}
      columns={columns}
      onCellsChanged={handleChanges}
      onContextMenu={handleContextMenu}
      enableRowSelection
    />
  );
}

render(<AdvancedContextMenuHandlingSample/>)

ReactGrid

Preview