2
votes

In my component I have a Table with generated rows from some custom array of objects. In the last TableCell I want to have a icon button that on click opens Menu with some MenuItem actions (edit and delete). This is my code:

{folders.map(folder => {
return (
    <TableRow key={folder.id} >
        <TableCell>{folder.name}</TableCell>
        <TableCell>
            <IconButton
                onClick={this.handleFolderActionClick}>
                <MoreHoriz />
            </IconButton>
            <Menu onClose={this.handleFolderActionClose} >
                <MenuItem onClick={event => {onEditFolder(event, folder)}}>
                    <ListItemIcon>
                        <Edit />
                    </ListItemIcon>
                    <ListItemText inset primary="Edit" />
                </MenuItem>
                <MenuItem onClick={{event => onDeleteFolder(event, folder)}}>
                    <ListItemIcon>
                        <Delete />
                    </ListItemIcon>
                    <ListItemText inset primary="Delete" />
                </MenuItem>
            </Menu>
        </TableCell>
    </TableRow>
);
})}

The onClick event is always passing the last folder element in the array and not the one mapped to that particular TableRow. I've read that MenuItem onClick event should not be used in this manner but I don't have any other idea how to solve my specific problem. I'm opened for any suggestions. How to pass object from the outer map function to onClick event of ManuItem?

Edit: Sandbox example

1

1 Answers

2
votes

You could try using currying to avoid inlining your menu item handlers and make them more readable (and avoid possible syntax-related bugs, which I believe you have). You would define the handlers like this:

onEditFolder = folder => event => {
  // edit click handler
}
onDeleteFolder = folder => event => {
  // delete click handler
}

And then use them in your render (inside your folders.map loop) like this:

<Menu onClose={this.handleFolderActionClose}>
  <MenuItem onClick={this.onEditFolder(folder)}>
    <ListItemIcon>
      <Edit />
    </ListItemIcon>
    <ListItemText inset primary="Edit" />
  </MenuItem>
  <MenuItem onClick={this.onDeleteFolder(folder)}>
    <ListItemIcon>
      <Delete />
    </ListItemIcon>
    <ListItemText inset primary="Delete" />
  </MenuItem>
</Menu>

You can do this because onEditFolder(folder) returns a function that expects an event, as per its definition. Same for onDeleteFolder(folder).

Note: I added the this keyword as a prefix for both functions, that would be assuming they're defined inside the same component where they are used. If you pass them as props, modify accordingly.

FOLLOW-UP: The error had nothing to do with the mapping, but was caused by both menus relying on the same boolean value to toggle open or close, causing the last menu to display regardless of which item was clicked.

I corrected this by creating a menus array in state, with length set to that of the size of the list and all values initialized to false (this is done in the componentDidMount lifecycle method). Menu open & close and close handlers were updated to pass the index of the list item, and updating the value in the menus array to true or false, accordingly. The open attribute of each Menu component was set to the corresponding entry in the menus array, in order to show/hide the menu based on the corresponding value.

Finally, both Menu components had the same id, I corrected that as well.

See working sandbox.