Search code examples
javascriptreactjsreact-hooksmaterial-table

React show/hide custom ContextMenu is lagging after toggling more than 2 times


I want to know why my browser is lagging just from showing/hiding a custom Context Menu I've created.

I have a React Material table and customized in a way that right clicking opens a context Menu with a list of items from the property. It does as its expected but after showing and hiding the component a couple of times, the browser just freezes. Why does this happen? And is there anyway to stop this from happening or an alternative way to do this?

Note: I've already tried the contextMenu package and it doesn't work well with MTableRow override, which is why I'm doing it this way.

import React, { useEffect, useState, forwardRef } from "react";
import MaterialTable, { MTableBodyRow } from "material-table";
import OptionContextMenu from "../OptionContextMenuComponent";

import AddBox from "@material-ui/icons/AddBox";
import ArrowDownward from "@material-ui/icons/ArrowDownward";
import Check from "@material-ui/icons/Check";
import ChevronLeft from "@material-ui/icons/ChevronLeft";
import ChevronRight from "@material-ui/icons/ChevronRight";
import Clear from "@material-ui/icons/Clear";
import DeleteOutline from "@material-ui/icons/DeleteOutline";
import Edit from "@material-ui/icons/Edit";
import FilterList from "@material-ui/icons/FilterList";
import FirstPage from "@material-ui/icons/FirstPage";
import LastPage from "@material-ui/icons/LastPage";
import Remove from "@material-ui/icons/Remove";
import SaveAlt from "@material-ui/icons/SaveAlt";
import Search from "@material-ui/icons/Search";
import ViewColumn from "@material-ui/icons/ViewColumn";

const tableIcons = {
  Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
  Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
  Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
  DetailPanel: forwardRef((props, ref) => (
    <ChevronRight {...props} ref={ref} />
  )),
  Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
  Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => (
    <ChevronLeft {...props} ref={ref} />
  )),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
  ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />),
  ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />)
};

const initialValue = {
  xPos: "0px",
  yPos: "0px",
  showMenu: false,
  options: []
};

//Show menu based on mouse position

const MyTable = () => {
  const [contextMenuProp, setContextMenuProp] = useState(initialValue);
  const onHandleContextMenu = (e, props) => {
    e.preventDefault();
    let newOptions = { ...contextMenuProp };
    newOptions.xPos = `${e.pageX}px`;
    newOptions.yPos = `${e.pageY}px`;
    newOptions.showMenu = true;
    newOptions.options = props.data.options;
    setContextMenuProp(newOptions);
    console.log("Right clicked", contextMenuProp);
  };

  // Re-hide menu
  const handleClick = (e, props) => {
    e.preventDefault();
    let newOptions = { ...contextMenuProp };
    newOptions.showMenu = false;
    newOptions.options = [];
    setContextMenuProp(newOptions);
    console.log("Left clicked");
  };

  return (
    <>
      <MaterialTable
        title="Dummy table"
        icons={tableIcons}
        columns={[
          { title: "Name", field: "name" },
          { title: "Surname", field: "surname" }
        ]}
        data={[
          { name: "Mehmet", surname: "Baran", options: ["work", "sleep"] },
          { name: "Zerya Betül", surname: "Baran", options: ["run", "walk"] }
        ]}
        options={{
          search: false,
          toolbar: false,
          paging: false,
          sorting: false,
          draggable: false,
          selection: false
        }}
        components={{
          Row: (props) => (
            <MTableBodyRow
              {...props}
              onContextMenu={(e) => onHandleContextMenu(e, props)}
            />
          )
        }}
        onRowClick={(e) => handleClick(e)}
      />
      <OptionContextMenu contextMenuProp={contextMenuProp} />
    </>
  );
};

export default MyTable;

The optionContextMenu component just displays what options are being passed.

Sample sandbox: sandbox


Solution

  • I faced the similar issue. It appears that there is a memory leak bug in material-table versions higher than 1.67. If a page with a table is rendered several times, it will be a memory leak. I would suggest you downgrade your material-table version.

    See this and this