Action overriding feature allows me to override button but it overrides all the action buttons. For example, if I have two action buttons for edit and delete and I use the Action overriding, both of my buttons get overridden with that same custom code.
How can I specify different codes for different buttons?
My final goal is to conditionally disable edit and delete buttons based on the rowData. I have tried with isEditable feature as shown in the code below but it doesn't work either.
...
....
const components = {
Action: props => (
<IconButton aria-label="delete" size="small"
disabled={props.data.email === 'admin#pipilika.com'}
onClick={(event) => props.action.onClick(event, props.data)}
>
<Icon>delete</Icon>
</IconButton>
)
};
const actions = [
{
icon: 'edit',
tooltip: 'Edit Index',
onClick: (event, rowData) => {
this.onEditClick(null, rowData._id);
}
},
{
icon: 'delete',
tooltip: 'Delete Index',
onClick: (event, rowData) => {
this.onDeleteClick(null, rowData._id);
}
},
];
const options= {
showTitle: false,
actionsColumnIndex: -1,
searchFieldStyle: {
color: "#fff"
}
};
const editable= {
isEditable: rowData => rowData.dataType === "html",
isDeletable: rowData => rowData.dataType === "html",
};
return(
<MaterialTable
editable={editable}
title="Created Index List"
columns={columns}
data={dataTypes}
actions={actions}
options={options}
components={components}
style={{overflow: 'hidden'}}
/>
);
for this specific use case, you can choose which Button to render based on checking the right icon name like this.
components={{
Action:
props => {
if(props.action.icon === 'edit'){
return(
<Button
onClick={(event) => props.action.onClick(event, props.data)}
color="primary"
variant="contained"
style={{textTransform: 'none'}}
size="small"
disabled
>
My Button
</Button>
)
}
if(props.action.icon === 'save'){
return(
<Button
onClick={(event) => props.action.onClick(event, props.data)}
color="primary"
variant="contained"
style={{textTransform: 'none'}}
size="small"
>
My Button
</Button>
)
}
}
}}
This solution worded form me:
import React, { Component } from 'react';
import {Button} from '#material-ui/core/';
import Icon from '#material-ui/core/Icon';
class ButtonBack extends Component {
constructor(props) {
super(props);
this.state={
onClick: props.onClick,
icon: props.icon
}
};
render() {
return (
<Button label="Regresar" onClick={this.state.onClick}>
<Icon>{this.state.icon}</Icon>
Regresar
</Button>
);
}
}
export default ButtonBack;
components={{
Action: props => {
if (typeof props.action === "function"){
var element= props.action(props.data);
return (
<IconButton aria-label={element.icon} size="small"
onClick={element.onClick}
>
<Icon>{element.icon}</Icon>
</IconButton>
)
}else{
if (props.action.icon==="keyboard_backspace"){
return (
<ButtonBack icon={props.action.icon}
onClick={props.action.onClick}
>
</ButtonBack>
)
}else{
return (
<IconButton aria-label={props.action.icon} size="small"
onClick={props.action.onClick}
>
<Icon>{props.action.icon}</Icon>
</IconButton>
)
}
}
}
}}
Related
I'm working on a simple todos app and I'm stuck at a point. Basically, my array filtering is not working. It's not doing anything in fact and I couldn't find out why. I'm using Material UI in the app and I'm suspecting there is something related to that but couldn't figure out entirely.
I'm trying to delete one todo by clicking the trash icon which triggers "deleteTodo" function. But it's not deleting it from the todos. Actually, as I said it's doing nothing. I'm keeeping my todos in the localStorage.
Here is my delete one todo function:
function deleteTodo(id) {
setTodos(todos.filter((todo,i,arr) => {
console.log("id:", id)
console.log("todo.id:",todo.id)
console.log("are equal:", todo.id === id)
console.log(i, arr)
return (todo.id !== id)
}))
}
console output:
[Log] id: – "37dbcd88d5a"
[Log] todo.id: – "37dbcd88d5a"
[Log] are equal: – true
[Log] 1 – [{text: "two", done: false, id: "7dbcd88d5a3"}, {text: "one", done: false, id: "37dbcd88d5a"}] (2)
my component as a whole:
import { uid } from 'uid'
import { useState , useEffect, useLayoutEffect, useRef } from "react"
import { Card, CardContent, Modal, List, ListItem, Box, Button, IconButton, TextField, Typography } from "#mui/material"
import styles from "../styles/Todos.module.css"
import { TransitionGroup } from 'react-transition-group';
import ReportProblemIcon from '#mui/icons-material/ReportProblem';
import DeleteIcon from '#mui/icons-material/Delete';
import EditIcon from '#mui/icons-material/Edit';
export default function Todos () {
const [ text, setText ] = useState("")
const [ todos, setTodos ] = useState([])
const [ showClearTodosModal, setShowClearTodosModal] = useState(false)
const inputRef = useRef()
useEffect(() => {
// localStorage.todos && console.log('b1:',JSON.parse(localStorage.todos))
if (localStorage.todos) {
// localStorage.todos && console.log(JSON.parse(localStorage.todos))
setTodos(JSON.parse(localStorage.todos))
} else {
localStorage.todos = []
}
// localStorage.todos && console.log('a1:',JSON.parse(localStorage.todos))
}, [])
useEffect(() => {
if (todos && todos.length > 0) {
localStorage.todos && console.log('b2:',JSON.parse(localStorage.todos))
localStorage.setItem("todos", JSON.stringify(todos))
localStorage.todos && console.log('a2:',JSON.parse(localStorage.todos))
}
}, [todos])
function handleSubmit(e) {
e.preventDefault()
const id = uid()
setTodos([{text, done:false, id:id}, ...todos])
setText("")
}
function markDone(e) {
setTodos(todos.map(todo => {
if (e.target.innerText === todo.text) {
return {...todo, done:!todo.done}
} else {
return todo
}
}))
}
function deleteTodo(id) {
setTodos(todos.filter((todo,i,arr) => {
console.log("id:", id)
console.log("todo.id:",todo.id)
console.log("are equal:", todo.id === id)
console.log(i, arr)
return (todo.id !== id)
}))
}
function deleteAll() {
setTodos([])
setShowClearTodosModal(false)
localStorage.removeItem("todos")
}
return (
<Box>
<form
onSubmit={handleSubmit}
>
<TextField
className={styles.entryfield}
label="Add a todo"
autoFocus
value={text}
onChange={(e) => setText(e.target.value)}
ref={inputRef}
/>
</form>
<Button
color="primary"
aria-label="upload picture"
component="span"
className={styles.clearall}
onClick={() => setShowClearTodosModal(true)}
startIcon={<ReportProblemIcon />}
>
Delete All Todos
</Button>
<Modal
open={showClearTodosModal}
onClose={() => setShowClearTodosModal(false)}
aria-labelledby="delete all todos"
aria-describedby="delete all todos"
className={styles.deleteallmodal}
>
<Box className={styles.modalbox}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Delete all todos?
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Are you sure you want to delete all todos? This action is irreversable and you will lose all of your todos.
</Typography>
<Button
onClick={() => setShowClearTodosModal(false)}
variant="contained"
sx={{m:1}}
>
Nah, don't delete my todos
</Button>
<Button
onClick={deleteAll}
variant="contained"
startIcon={<ReportProblemIcon />}
sx={{
m:1,
color: "maroon",
}}
>
Yes I'm sure delete all of them
</Button>
</Box>
</Modal>
<List>
{todos && todos.map(todo =>(
<ListItem
key={todo.id}
onClick={markDone}
>
<Card className={styles.card}
>
<IconButton
className={styles.icons}
onClick={() => {deleteTodo(todo.id)}}
aria-label="delete"
>
<DeleteIcon fontSize="small"/>
</IconButton>
<IconButton
className={styles.icons}
aria-label="edit"
>
<EditIcon fontSize="small"/>
</IconButton>
<Typography
variant="body1"
style= {{
color: todo.done ? "#555" : "",
margin: 10,
}}
>
{todo.text}
</Typography>
</Card>
</ListItem>
)
)}
</List>
</Box>
)
}
Here is the working codesandbox of your component. Please align your code with my example and be sure that you do not have any other wrong implementations in your code
https://codesandbox.io/s/heuristic-shadow-7lz1o0?file=/src/App.js
import { useState } from "react";
import "./styles.css";
export default function App() {
const [todos, setTodos] = useState([
{ text: "two", done: false, id: "7dbcd88d5a3" },
{ text: "one", done: false, id: "37dbcd88d5a" },
{ text: "one", done: false, id: "643dbcd88d5a" }
]);
function deleteTodo(id) {
setTodos(
todos.filter((todo, i, arr) => {
console.log("id:", id);
console.log("todo.id:", todo.id);
console.log("are equal:", todo.id === id);
console.log(i, arr);
return todo.id !== id;
})
);
}
return (
<div className="App">
{todos &&
todos.map((todo) => (
<div key={todo.id}>
<div>
<button
onClick={() => {
deleteTodo(todo.id);
}}
aria-label="delete"
>
{todo.id}
</button>
</div>
</div>
))}
</div>
);
}
let testArray = [{text: "two", done: false, id: "7dbcd88d5a3"}, {text: "one", done: false, id: "37dbcd88d5a"},
{text: "one", done: false, id: "643dbcd88d5a"}]
function deleteTodo(id) {
console.log(testArray.filter((todo,i,arr) => {
return (todo.id !== id)}))
}
deleteTodo('7dbcd88d5a3');
Thanks for sharing your full code! It was helpful to understand what was happening.
The culprit line that's causing you the headache is here:
<ListItem
key={todo.id}
onClick={markDone}
>
<Card className={styles.card}
>
<IconButton
className={styles.icons}
onClick={() => {deleteTodo(todo.id)}}
aria-label="delete"
>
Specifically, onClick={markDone} on ListItem. You put the markDone function on the whole list item. So clicking on the IconButton causes the ListItem event to be triggered too.
I know you have the e.preventDefault() but unfortunately the order is reversed here with event bubbling (starts inside out and you need stopPropagation instead of preventDefault): I put some logs in deleteTodo and markDone to indicate the start and end of each function and this is the order. This was the output:
Todos.tsx:82 deleteTodo start
Todos.tsx:86 deleteTodo end
Todos.tsx:66 markdone start
Todos.tsx:76 markdone end
So what's likely happening is react is either doing the setTodos sequentially or batching them, but either way it looks like the setTodos from markDone is taking precedence, and the setTodo there is just a map of the same values again. Hence why clicking the delete button leads to no changes and the same todo items remaining.
Possible solutions could include
a) Add the event as a parameter to deleteTodo and run e.stopPropagation() at the top of the function to prevent event bubbling further.
function deleteTodo(e, id) {
e.stopPropagation();
console.log('deleteTodo start');
setTodos(
todos.filter((todo, i, arr) => {
return todo.id !== id;
})
);
console.log('deleteTodo end');
}
and setup your IconButton:
<IconButton
onClick={(e) => {
deleteTodo(e, todo.id);
}}
This should help stop the event from bubbling further up (I confirmed this to be working locally).
b) You could remove the onClick={markDone} and add a separate button to do the mark done action (removing the onClick is confirmed to make your delete method work)
c) You keep the markDone but do some additional conditional checking on the e event target to see if the delete button was hit - if it was, return early in the function.
I'm using a mui-datatable where the onRowClick will direct the user to another page. I also have custom action button in each row. However, If i'll click the custom action button, it will also trigger the onRowClick which will direct the user to another page. How can I prevent the onRowClick when clicking the custom action button?
class orders extends Component {
constructor() {
super();
this.state = { orders: [] };
}
handleRowClick = (rowData, rowMeta) => {
this.props.history.push("/details", `${rowData[0]}`);
};
columns = [
"Order ID",
"Items",
"Address",
"Total Amount",
{
name: "Action Button",
options: {
filter: true,
sort: false,
empty: true,
customBodyRender: (value, tableMeta) => {
return (
<FormControlLabel
value={value}
control={
<Button
value={value} >
Action Button
</Button>
}
onClick={(e) => {
//firestore codes
}}
/>
);
},
},
},
];
options = {
onRowClick: this.handleRowClick,
};
render() {
return this.state.orders ? (
<div>
<MUIDataTable
title={" Orders"}
columns={this.columns}
data={this.state.orders}
options={this.options}
/>
</div>
) : (
<p>Loading...</p>
);
}
}
export default withRouter(orders);
Assuming you have a button
<button onClick={onClick} >Don't Bubble</button>
You may use event.stopPropagation() to prevent event from bubbling up to parent.
const onClick=(event) => {
event.stopPropagation()
.. do what u want with the button
}
disable row event click for action colmun
const handleActionColmunClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
};
<TableBodyCell onClick={handleActionColmunClick}>
<IconButton
aria-label="more"
aria-controls="customized-menu"
aria-haspopup="true"
onClick={handleActionMenuClick}
>
.....
...
..
.
I have a problem with react-bootstrap-sweetalert library in react. Actually it works fine, untill slow internet connection. When someone tries to click submit button, because of the slow internet (I'm simulating it through "Network section [Slow 3G]") alert is not closing exactly at time after clicking a button, but after several seconds. So, there is probability that someone can click several times submit button. It is a problem, because several same requests can flow to backend and database. In other sections without using a library I can just "disable" react states after handling onClick.
So question is - to disable button in react-bootstrap-sweetalert library after handling onConfirm function.
Code:
handleSubmitInvoice = () => {
this.setState({
sweetalert: (
<SweetAlert
warning
showCancel
confirmBtnText={this.state.alert.label.sure}
cancelBtnText={this.state.alert.label.cancel}
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title={this.state.alert.label.areyousure}
onConfirm={() => this.submit()}
onCancel={() => this.hideAlert()}
>
{this.state.alert.confirmSubmit}
</SweetAlert>
)
});
};
in render():
<button
className="btn btn-success btn-sm"
onClick={this.handleSubmitInvoice}
>
submit
</button>
submit function:
submit = () => {
const req = { invoice: this.state.invoiceNumber };
Axios.post("/api", req)
.then(() => {
this.props.history.push({
pathname: "/mypathname",
state: {
fromSubmitInvoice: true
}
});
})
.catch(err => {
Alert.error(
err.response.data.code === "internal_error"
? this.state.alert.raiseError
: err.response.data.text,
{
position: "top-right",
effect: "bouncyflip",
timeout: 2000
}
);
this.hideAlert();
});
};
Codesandbox: https://codesandbox.io/s/sweet-alert-problem-ktzcb
Thanks in advance.
Problem solved try this out
import React, { Component } from "react";
import SweetAlert from "react-bootstrap-sweetalert";
import ReactDOM from "react-dom";
const SweetAlertFunction = ({ show, disableButton, submit, hideAlert }) => {
return (
<SweetAlert
warning
show={show}
showCancel
confirmBtnText="confirmBtnText"
cancelBtnText="cancelBtnText"
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title="title"
onConfirm={submit}
onCancel={hideAlert}
>
submit
</SweetAlert>
);
};
export default class HelloWorld extends Component {
constructor(props) {
super(props);
this.state = {
disableButton: false,
show: false
};
}
hideAlert() {
this.setState({
show: false
});
}
submit() {
this.setState({ disableButton: true });
console.log("submit");
setTimeout(() => {
this.setState({ disableButton: false });
}, 3000);
}
render() {
const { show, disableButton } = this.state;
console.log("disableButton", disableButton);
return (
<div style={{ padding: "20px" }}>
<SweetAlertFunction
show={show}
disableButton={disableButton}
submit={() => this.submit()}
hideAlert={() => this.hideAlert()}
/>
<button
className="btn btn-success btn-sm"
onClick={() => this.setState({ show: true })}
>
Click
</button>
</div>
);
}
}
ReactDOM.render(<HelloWorld />, document.getElementById("app"));
In your case, since you are assigning the Sweetalert component to the sweetalert state, you need to have a local state that controls the disabled state, but to make it simple, you can make sweetalert state control the visibility/presence of the Sweetalert component, like below:
handleSubmitInvoice() {
// just set sweetalert to true to show the Sweetalert component
this.setState({ sweetalert: true });
}
render() {
const { sweetalert, disableButton } = this.state;
return (
<div style={{ padding: "20px" }}>
// this makes disableButton reactive and pass it automatically to Sweetalert component
{sweetalert && (
<SweetAlert
warning
showCancel
confirmBtnText="confirmBtnText"
cancelBtnText="cancelBtnText"
confirmBtnBsStyle="success"
cancelBtnBsStyle="default"
disabled={disableButton}
title="title"
onConfirm={() => this.submit()}
onCancel={() => this.hideAlert()}
>
submit
</SweetAlert>
)}
<button
className="btn btn-success btn-sm"
onClick={this.handleSubmitInvoice}
>
Click
</button>
</div>
);
}
You can see it in this sandbox https://codesandbox.io/s/sweet-alert-problem-lv0l5
P.S. I added setTimeout in submit to make disabling of button noticeable.
i have a question . I have to build an app where people can search for music from "lastFm" . So far so good , i already made few things to works normal , but i have a problem with if/else in map function , i've try to show user "no result found" if there are any , but with no luck .If there is 1+ results , will be displayed on the screen , but if there are any , nothing happen . Here is my code .
import React, { Component } from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import {
TextField,
Button,
List,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Card,
CardContent
} from '#material-ui/core';
import axios from 'axios';
import './App.css';
const API_URL = 'http://ws.audioscrobbler.com/2.0/?limit=5&format=json&method=artist.search&api_key=' + process.env.REACT_APP_LASTFM_APPKEY;
const isEmpty = (str) => str.length === 0;
class App extends Component {
state = {
searchTerm: '',
savedArtists: []
}
componentDidMount() {
const existing = localStorage.getItem('savedArtists')
if (existing) {
this.setState({ savedArtists: JSON.parse(existing) })
}
}
onTextChange = (event) => {
const value = event.target.value;
this.setState({ searchTerm: value });
}
search = (terms) => {
const request = API_URL + '&artist=' + terms;
axios.get(request).then((response) => {
const results = response.data.results;
const artists = results.artistmatches.artist.map((artist) => {
const avatarImage = artist.image.find(image => image.size === 'medium');
const imageUrl = avatarImage['#text'];
return { ...artist, avatar: imageUrl }
});
this.setState({ artists });
})
}
onSearchClick = () => {
this.search(this.state.searchTerm);
}
clearSearch = () => {
this.setState({
searchTerm: '',
artists: []
})
}
onResultClick = (artist) => {
this.clearSearch();
const savedArtists = this.state.savedArtists;
savedArtists.push(artist)
this.setState({ savedArtists: savedArtists })
localStorage.setItem('savedArtists', JSON.stringify(savedArtists));
}
render() {
const results = this.state.artists || [];
return (
<div className="App">
<header className="App-header">
<AppBar position="static" color="primary">
<Toolbar className="search-bar">
<Typography variant="h6" color="inherit">
Photos
</Typography>
<TextField
placeholder="Search on Last.fm"
className="search-input"
onChange={this.onTextChange}
value={this.state.searchTerm}
/>
<Button
onClick={this.onSearchClick}
variant="contained"
color="secondary"
disabled={isEmpty(this.state.searchTerm)}
>
Search
</Button>
{!isEmpty(this.state.searchTerm) && (
<Button
onClick={this.clearSearch}
variant="contained"
>
Clear
</Button>)
}
</Toolbar>
</AppBar>
</header>
//****Here is where i've try to use if/else
<List className="search-results">
{
results.map((artist ,results) => {
if(results.length === 0)
return (<ListItem> Not Found</ListItem>
); else {
return ( <ListItem
button
key={artist.name}
className="result"
onClick={() => this.onResultClick(artist)}
>
<ListItemAvatar>
<Avatar src={artist.avatar} alt={artist.name} />
</ListItemAvatar>
<ListItemText primary={artist.name} />
<Button
variant="outlined"
color="secondary"
size="small"
className="add-button"
>
Add to favorites
</Button>
</ListItem>);
}
})
}
</List>
<div className="artist-container">
{
this.state.savedArtists.map((artist, i) => {
return (
<Card className="artist-card"
key={i}
>
<CardContent>
{artist.name}
</CardContent>
</Card>
)
})
}
</div>
</div>
);
}
}
export default App;
You're having an error there. It's .map(result: any, index: number, original: []), so you're referring to an index number with argument results:
results.map((artist, results) => {
if(results.length === 0) { ... }
});
So fix it just by not referring to results as a argument of .map
The problem is that you're trying to do an if/else in the map of the array. But if the array has no items, then there is nothing to map.
What to do is to use a ternary to check if the array has any results:
{ results && result.length ?
<List className="search-results">
{
results.map((artist) => {
return (
<ListItem button key={artist.name} className="result" onClick={() => this.onResultClick(artist)} >
<ListItemAvatar>
<Avatar src={artist.avatar} alt={artist.name} />
</ListItemAvatar>
<ListItemText primary={artist.name} />
<Button
variant="outlined"
color="secondary"
size="small"
className="add-button"
>
Add to favorites
</Button>
</ListItem>
);
})
}
</List>
: <div>No Results</div>
}
Here, we're checking if results.length is considered truthy or not, if it's 1 or higher, then it will render your list, otherwise it will render our div informing the user there is no results, which you can change out to be whatever you want.
I am new to material-ui and React and I have a requirement to create multiple menus dynamically in a loop. Please find the code snippet as:
state = {
anchorEl: null,
};
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { anchorEl } = this.state;
let items = _.map(results, (item, index) => {
return (
<ListItem
key={item.ID}
divider
>
<ListItemSecondaryAction>
<IconButton
aria-label="More"
aria-owns={anchorEl ? 'long-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={this.handleClose}
PaperProps={{
style: {
maxHeight: 200,
width: 200,
},
}}
>
<MenuItem>
<IconButton onClick={() => this.props.delete(item.ID)} >
Delete entry<DeleteIcon />
</IconButton>
</MenuItem>
</Menu>
<ListItemSecondaryAction>
</ListItem>
)
})
return (
<Fragment>
<List>
{items}
</List>
</Fragment>
)
}
Now, with the above code, the menus work fine and the UI is good. But whenever I try to delete an entry by clicking on Delete icon inside the menu, always the last entry is deleted i.e. item.ID passes the value of the last element and the last entry is deleted.
Is there a way I can create unique menuitems for each entry and manage the state in such a way which makes sure that the correct item is deleted and not the last one always.
Note: 'results' is any list loaded dynamically and 'delete' function implements the functionality to delete the corresponding entry
Thanks in advance.
I would suggest use another child component for render your list item. In your current example you only one anchorEl, which means wherever you click, always one menu open and take action of that, which is last one. If you have child component for menu item, each component will have there own state and work for that item only.
Example
class Main extends Component {
render() {
let items = _.map(results, (item, index) => {
return (
<MenuItemComponent key={item.ID} item={item} onClick={this.handleClick} onDelete={(item) => this.props.delete(item.ID)} />
)
})
return (
<Fragment>
<List>
{items}
</List>
</Fragment>
)
}
}
class MenuItemComponent extends Component {
state = {
anchorEl: null,
};
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { item } = this.props;
const { anchorEl } = this.state;
return (
<ListItem
divider
>
<ListItemSecondaryAction>
<IconButton
aria-label="More"
aria-owns={anchorEl ? 'long-menu' : null}
aria-haspopup="true"
onClick={this.handleClick.bind(this)}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={this.handleClose.bind(this)}
PaperProps={{
style: {
maxHeight: 200,
width: 200,
},
}}
>
<MenuItem>
<IconButton onClick={() => this.props.onDelete(item)} >
Delete entry<DeleteIcon />
</IconButton>
</MenuItem>
</Menu>
</ListItemSecondaryAction>
</ListItem>
)
}
}
Here's a working example https://codesandbox.io/s/nn555l48xm
import * as React from "react";
import {
Menu,
MenuItem,
IconButton
} from "#material-ui/core";
import MoreVertIcon from "#material-ui/icons/MoreVert";
export default function Demo() {
const [openElem, setOpenElem] = React.useState(null);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (elem) => (event) => {
setAnchorEl(event.currentTarget);
setOpenElem(elem);
};
const handleClose = () => {
setAnchorEl(null);
setOpenElem(null);
};
let arr = [0, 1, 2];
let body = arr.map((elem) => {
return (
<div key={elem}>
<IconButton
aria-label="more"
aria-controls={"long-menu" + elem}
aria-haspopup="true"
onClick={handleClick(elem)}
>
<MoreVertIcon />
</IconButton>
<Menu
id={"long-menu" + elem}
anchorEl={anchorEl}
keepMounted
open={openElem === elem}
onClose={handleClose}
>
<MenuItem
onClick={(e) => {
handleClose();
}}
>
{elem}
</MenuItem>
</Menu>
</div>
);
});
return <div>{body}</div>;
}