MUI context menu in a list always has reference to the last item

I’m using Material UI for a context menu in a react app. I’ve made a min repro here:

Edit summer-silence-xzxwd3

export default function App() {
  const [contextMenu, setContextMenu] = React.useState(null);

  const myArray = ["item1", "item2", "item3"];

  const handleContextMenu = (event) => {
    event.preventDefault();
    setContextMenu(
      contextMenu === null
        ? {
            mouseX: event.clientX + 2,
            mouseY: event.clientY - 6
          }
        : null
    );
  };

  const editItem = (i) => {
    setContextMenu(null);
    alert(`Editing ${i}`);
  };

  return (
    <div>
      {myArray.map((i) => (
        <div key={i} onContextMenu={handleContextMenu}>
          <Card sx={{ backgroundColor: "gray", m: 2 }}>
            <CardContent>
              <Typography>{i}</Typography>
            </CardContent>
          </Card>
          <Menu
            open={contextMenu != null}
            onClose={() => setContextMenu(null)}
            anchorReference="anchorPosition"
            anchorPosition={
              contextMenu != null
                ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
                : undefined
            }
          >
            <MenuItem onClick={() => editItem(i)}>Edit</MenuItem>
          </Menu>
        </div>
      ))}
    </div>
  );
}

Every time I right-click > "Edit", the alert always shows for the last item ("item3"). In my full app, I can also drag-drop to rearrange these cards and the behavior will still be whatever card is last in the list after the rearrange.

Not sure what I’m doing wrong here. I thought it might be a missing key issue, but I don’t think that’s the case as the only list item with siblings (the div) has a key. Any ideas?

>Solution :

If you inspect your page, you can see that all 3 menus open at the same time, and the last one always receives the click event.

Here is how you can fix your code:

import "./styles.css";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import * as React from "react";

export default function App() {
  const [contextMenu, setContextMenu] = React.useState(null);

  const myArray = ["item1", "item2", "item3"];

  const handleContextMenu = (item) => (event) => {
    event.preventDefault();
    setContextMenu(
      contextMenu === null
        ? {
            item,
            mouseX: event.clientX + 2,
            mouseY: event.clientY - 6
          }
        : null
    );
  };

  const editItem = () => {
    alert(`Editing ${contextMenu.item}`);
    setContextMenu(null);
  };

  return (
    <div>
      {myArray.map((i) => (
        <div key={i} onContextMenu={handleContextMenu(i)}>
          <Card sx={{ backgroundColor: "gray", m: 2 }}>
            <CardContent>
              <Typography>{i}</Typography>
            </CardContent>
          </Card>
        </div>
      ))}

      <Menu
        open={contextMenu != null}
        onClose={() => setContextMenu(null)}
        anchorReference="anchorPosition"
        anchorPosition={
          contextMenu != null
            ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
            : undefined
        }
      >
        <MenuItem onClick={editItem}>Edit</MenuItem>
      </Menu>
    </div>
  );
}

Leave a Reply