I am currently studying Material UI 5 and specifically Context Menu subject - https://mui.com/material-ui/react-menu/#context-menu.
Can someone explain me why the handleClose function always print the last index in the array - 4?
import * as React from "react"
import Menu from "#mui/material/Menu"
import MenuItem from "#mui/material/MenuItem"
import Typography from "#mui/material/Typography"
export default function ContextMenu() {
const [contextMenu, setContextMenu] = React.useState(null)
const handleContextMenu = (event) => {
event.preventDefault()
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX + 2,
mouseY: event.clientY - 6,
}
: null
)
}
const handleClose = (i) => {
setContextMenu(null)
console.log(i)
}
return (
<>
{["a", "b", "c", "d", "e"].map((item, index) => (
<div
key={index}
onContextMenu={handleContextMenu}
style={{ cursor: "context-menu" }}
>
<Typography variant='h6' component='h1' sx={{ padding: 4 }}>
{item}
</Typography>
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference='anchorPosition'
anchorPosition={
contextMenu !== null
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
: undefined
}
>
<MenuItem onClick={() => handleClose(index)}>one</MenuItem>
<MenuItem onClick={() => handleClose(index)}>two</MenuItem>
<MenuItem onClick={() => handleClose(index)}>three</MenuItem>
</Menu>
</div>
))}
</>
)
}
The way you currently have the on clicks setup is that it accesses the value index has at the time you click it, not the value it had when the function was created.
Change
onClick={() => handleClose(index)}
To
onClick={((index) => () => handleClose(index))(index)}
check the following code and see how you should manage user's options :
import * as React from "react";
import Menu from "#mui/material/Menu";
import MenuItem from "#mui/material/MenuItem";
import Typography from "#mui/material/Typography";
export default function ContextMenu() {
const initialState = {
element: null,
item: null
};
const [state, setState] = React.useState(initialState);
const handleListItemClick = (element, item) => {
setState({
element,
item
});
};
const handleMenuItemClick = (option) => {
setState(initialState);
switch (option) {
case "one":
console.log(state.item, "one");
break;
case "two":
console.log(state.item, "two");
break;
default:
break;
}
};
return (
<div>
{React.Children.toArray(
["a", "b", "c", "d", "e"].map((item) => (
<Typography
onClick={(e) => handleListItemClick(e.target, item)}
sx={{ margin: 4 }}
>
{item}
</Typography>
))
)}
<Menu open={state.element !== null} anchorEl={state.element}>
<MenuItem onClick={() => handleMenuItemClick("one")}>one</MenuItem>
<MenuItem onClick={() => handleMenuItemClick("two")}>two</MenuItem>
</Menu>
</div>
);
}
codesandbox: https://codesandbox.io/s/great-microservice-w5jh00
Related
It's completely a question for material ui.
Now My code is:
import Grid from "#mui/material/Grid";
import List from "#mui/material/List";
import ListItem from "#mui/material/ListItem";
import ListItemIcon from "#mui/material/ListItemIcon";
import ListItemText from "#mui/material/ListItemText";
import Checkbox from "#mui/material/Checkbox";
import Button from "#mui/material/Button";
import Paper from "#mui/material/Paper";
import React, { useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setRightEvaluation } from "slices/UserSearchTransferList";
interface TransferListProps {
items: any;
}
function not(a: readonly number[], b: readonly number[]) {
console.log(a.filter((value) => b.indexOf(value) === -1));
return a.filter((value) => b.indexOf(value) === -1);
}
function intersection(a: readonly number[], b: readonly number[]) {
return a.filter((value) => b.indexOf(value) !== -1);
}
const TransferListComponent = React.memo((props: TransferListProps) => {
const rightEvaluation = useSelector(setRightEvaluation);
const dispatch = useDispatch();
const [checked, setChecked] = React.useState<readonly any[]>([]);
const [left, setLeft] = React.useState<any[]>(
props.items.map((value: any) => {
return value.name;
})
);
const leftChecked = intersection(checked, left);
const rightChecked = intersection(
checked,
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation
);
const handleToggle = useMemo(
() => (value: number) => () => {
const currentIndex = checked.indexOf(value);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(value);
} else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
},
[rightChecked]
);
const handleAllRight = useMemo(
() => () => {
dispatch(
setRightEvaluation(
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation.concat(
left
)
)
);
setLeft([]);
},
[rightEvaluation.payload.rightEvaluationReducer.rightEvaluation, left]
);
const handleCheckedRight = useMemo(
() => () => {
dispatch(
setRightEvaluation(
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation.concat(
leftChecked
)
)
);
setLeft(not(left, leftChecked));
setChecked(not(checked, leftChecked));
},
[
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation,
left,
checked,
]
);
const handleCheckedLeft = useMemo(
() => () => {
setLeft(left.concat(rightChecked));
dispatch(
setRightEvaluation(
not(
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation,
rightChecked
)
)
);
setChecked(not(checked, rightChecked));
},
[
left,
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation,
checked,
]
);
const handleAllLeft = useMemo(
() => () => {
setLeft(
left.concat(
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation
)
);
dispatch(setRightEvaluation([]));
},
[left, rightEvaluation.payload.rightEvaluationReducer.rightEvaluation]
);
const customList = useMemo(
() => (items: string[]) =>
(
<Paper sx={{ width: 175, height: 230, overflow: "auto" }}>
<List dense component="div" role="list">
{items.map((value: any) => {
const labelId = `transfer-list-item-${value}-label`;
const randomNumber = Math.random();
return (
<ListItem
key={randomNumber}
role="listitem"
button
onClick={handleToggle(value)}
>
<ListItemIcon>
<Checkbox
checked={checked.indexOf(value) !== -1}
tabIndex={-1}
disableRipple
inputProps={{
"aria-labelledby": labelId,
}}
/>
</ListItemIcon>
<ListItemText id={labelId} primary={`${value}`} />
</ListItem>
);
})}
<ListItem />
</List>
</Paper>
),
[rightChecked]
);
return (
<Grid container spacing={2} justifyContent="start" alignItems="center">
<Grid item>{customList(left)}</Grid>
<Grid item>
<Grid container direction="column" alignItems="center">
<Button
sx={{ my: 0.5 }}
variant="outlined"
size="small"
onClick={handleAllRight}
disabled={left.length === 0}
aria-label="move all right"
>
≫
</Button>
<Button
sx={{ my: 0.5 }}
variant="outlined"
size="small"
onClick={handleCheckedRight}
disabled={leftChecked.length === 0}
aria-label="move selected right"
>
>
</Button>
<Button
sx={{ my: 0.5 }}
variant="outlined"
size="small"
onClick={handleCheckedLeft}
disabled={rightChecked.length === 0}
aria-label="move selected left"
>
<
</Button>
<Button
sx={{ my: 0.5 }}
variant="outlined"
size="small"
onClick={handleAllLeft}
disabled={
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation
.length === 0
}
aria-label="move all left"
>
≪
</Button>
</Grid>
</Grid>
<Grid item>
{customList(
rightEvaluation.payload.rightEvaluationReducer.rightEvaluation
)}
</Grid>
</Grid>
);
});
export default function TransferListEvaluations(props: TransferListProps) {
return <TransferListComponent {...{ items: props.items }} />;
}
Let me explain the code.
User can move those name as string to the right side.
Then finally the right side will send rails.
For saving right side data , I'm using redux.
const [left, setLeft] = React.useState<any[]>(
props.items.map((value: any) => {
return value.name;
})
);
The problem is rails.
I need to make search system in rails.
So I made this code.
User
.order('users.created_at DESC')
.includes(:evaluations, :skills, :positions)
.where(
user_evaluations: {
evaluation_id: search_user[:evaluation],
},
),
The problem is that I'm using id of relation table.
So I did this stupid way.
evaluation = []
params[:evaluation].map do |value|
if value == 'A'
evaluation.push(1)
elsif value == 'B'
evaluation.push(2)
elsif value == 'C'
evaluation.push(3)
elsif value == 'D'
evaluation.push(4)
elsif value == 'E'
evaluation.push(5)
elsif value == 'F'
evaluation.push(6)
elsif value == 'G'
evaluation.push(7)
elsif value == 'H'
evaluation.push(8)
elsif value == 'I'
evaluation.push(9)
elsif value == 'J'
evaluation.push(10)
elsif value == 'K'
evaluation.push(11)
end
end
I think it's better get id from react.
How can I do that?
Thank you.
I've used material-UI stepper to make multi-step form inside of the Drawer content which also a material-UI Drawer. But when I use that stepper code it looks like the below picture.
So, where is the error?
And that stepper code would be like:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepButton from '#material-ui/core/StepButton';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
},
button: {
marginRight: theme.spacing(1),
},
backButton: {
marginRight: theme.spacing(1),
},
completed: {
display: 'inline-block',
},
instructions: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
}));
function getSteps() {
return ['Select campaign settings', 'Create an ad group', 'Create an ad'];
}
function getStepContent(step) {
switch (step) {
case 0:
return 'Step 1: Select campaign settings...';
case 1:
return 'Step 2: What is an ad group anyways?';
case 2:
return 'Step 3: This is the bit I really care about!';
default:
return 'Unknown step';
}
}
export default function HorizontalNonLinearAlternativeLabelStepper() {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
const [completed, setCompleted] = React.useState(new Set());
const [skipped, setSkipped] = React.useState(new Set());
const steps = getSteps();
const totalSteps = () => {
return getSteps().length;
};
const isStepOptional = (step) => {
return step === 1;
};
const handleSkip = () => {
if (!isStepOptional(activeStep)) {
// You probably want to guard against something like this
// it should never occur unless someone's actively trying to break something.
throw new Error("You can't skip a step that isn't optional.");
}
setActiveStep((prevActiveStep) => prevActiveStep + 1);
setSkipped((prevSkipped) => {
const newSkipped = new Set(prevSkipped.values());
newSkipped.add(activeStep);
return newSkipped;
});
};
const skippedSteps = () => {
return skipped.size;
};
const completedSteps = () => {
return completed.size;
};
const allStepsCompleted = () => {
return completedSteps() === totalSteps() - skippedSteps();
};
const isLastStep = () => {
return activeStep === totalSteps() - 1;
};
const handleNext = () => {
const newActiveStep =
isLastStep() && !allStepsCompleted()
? // It's the last step, but not all steps have been completed
// find the first step that has been completed
steps.findIndex((step, i) => !completed.has(i))
: activeStep + 1;
setActiveStep(newActiveStep);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleStep = (step) => () => {
setActiveStep(step);
};
const handleComplete = () => {
const newCompleted = new Set(completed);
newCompleted.add(activeStep);
setCompleted(newCompleted);
/**
* Sigh... it would be much nicer to replace the following if conditional with
* `if (!this.allStepsComplete())` however state is not set when we do this,
* thus we have to resort to not being very DRY.
*/
if (completed.size !== totalSteps() - skippedSteps()) {
handleNext();
}
};
const handleReset = () => {
setActiveStep(0);
setCompleted(new Set());
setSkipped(new Set());
};
const isStepSkipped = (step) => {
return skipped.has(step);
};
function isStepComplete(step) {
return completed.has(step);
}
return (
<div className={classes.root}>
<Stepper alternativeLabel nonLinear activeStep={activeStep}>
{steps.map((label, index) => {
const stepProps = {};
const buttonProps = {};
if (isStepOptional(index)) {
buttonProps.optional = <Typography variant="caption">Optional</Typography>;
}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepButton
onClick={handleStep(index)}
completed={isStepComplete(index)}
{...buttonProps}
>
{label}
</StepButton>
</Step>
);
})}
</Stepper>
<div>
{allStepsCompleted() ? (
<div>
<Typography className={classes.instructions}>
All steps completed - you're finished
</Typography>
<Button onClick={handleReset}>Reset</Button>
</div>
) : (
<div>
<Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography>
<div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={handleNext}
className={classes.button}
>
Next
</Button>
{isStepOptional(activeStep) && !completed.has(activeStep) && (
<Button
variant="contained"
color="primary"
onClick={handleSkip}
className={classes.button}
>
Skip
</Button>
)}
{activeStep !== steps.length &&
(completed.has(activeStep) ? (
<Typography variant="caption" className={classes.completed}>
Step {activeStep + 1} already completed
</Typography>
) : (
<Button variant="contained" color="primary" onClick={handleComplete}>
{completedSteps() === totalSteps() - 1 ? 'Finish' : 'Complete Step'}
</Button>
))}
</div>
</div>
)}
</div>
</div>
);
}
I just changed the width. But it doesn't work. How can I correct this error?
I just found out the answer to that. In useStyles (styles in my code) I added marginRight: 700, to root class. Then the stepper stretched.
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
marginRight: 700,
},
Now it's look like this.
I am using react-hooks to manage a list of JSX.Elements. However, once the element changed, trying to delete it will cause unexpected behavior.
I had tried using useReducer, remove by index etc, still unexpected updated result occurred.
FolderPage.tsx
import React, { useState, useEffect } from 'react';
import { Button, Box, Grid } from 'grommet';
import { Add, Close } from 'grommet-icons';
import { Files } from '../Component/Files/Files';
import { ePub } from '../extension/ePub/ePub';
interface Props {}
export const FolderPage: React.FC<Props> = () => {
const [state, setState] = useState([<Files openFileHandlers={[ePub]} />]);
const newFolderPanel = () => setState(prev => prev.concat(<Files openFileHandlers={[ePub]} />));
const removePanel = (panel: JSX.Element) => setState(prevState => prevState.filter(s => s !== panel));
return (
<div>
<Box align="start" pad="xsmall">
<Button icon={<Add />} label="New Folder Panel" onClick={newFolderPanel} primary />
</Box>
{state.map((s, index) => (
<Grid key={index}>
<Box align="end">
<Button
icon={<Close color="white" style={{ backgroundColor: 'red', borderRadius: '50%', padding: '.25rem' }} />}
type="button"
onClick={() => removePanel(s)}
/>
</Box>
{s}
</Grid>
))}
</div>
);
};
For example, in usage:
What should I change my code so my delete click will delete the matched element?
There is a way to work around it. For each item inside the array. Instead of storing item directly, stored it as {id: yourAssignedNumber, content: item}.
In this way, you could have control of the id, and remove by comparing the id only. This way, it will work correctly.
import React, { useState, useRef } from 'react';
import { Button, Row, Col } from 'antd';
import { Files } from '../Components/Files/Files';
import { fileHandler } from '../model/fileHandler';
interface Props {
fileHandlers?: fileHandler[];
}
export const FolderPage: React.FC<Props> = ({ fileHandlers }) => {
const [state, setState] = useState([{ key: -1, content: <Files fileHandlers={fileHandlers} /> }]);
const key = useRef(0);
const newFolderPanel = () =>
setState(prev =>
prev.concat({
key: key.current++,
content: <Files fileHandlers={fileHandlers} />
})
);
const removePanel = (key: number) => setState(prevState => prevState.filter(s => s.key !== key));
return (
<Row>
<Button type="primary" icon="plus" onClick={newFolderPanel} style={{ margin: '.75rem' }}>
New Foldr Panel
</Button>
{state.map(({ key, content }) => (
<Col key={key}>
<div
style={{
background: '#75ff8133',
display: 'grid',
justifyItems: 'end',
padding: '.5rem 1rem'
}}>
<Button onClick={() => removePanel(key)} icon="close" type="danger" shape="circle" />
</div>
{content}
</Col>
))}
</Row>
);
};
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>;
}