yung peso
yung peso

Reputation: 1766

Mapping over Menu objects causing multiple menus to open due to state

I'm having an issue with my Material UI <Menu>. Basically, I'm mapping over a <Card> to display some data and interface functionality. I'm trying to add a <IconButton> on each card. Once, clicked, the button opens a menu. Currently the issue I'm facing is, when I click on the IconButton, all menu's open up on top of each other (due to mapping over menu items with the same state prop).

                                 <CardHeader   
                                    action={
                                        <>
                                        <IconButton 
                                            onClick={handleClick}
                                            aria-label="settings">
                                                <MoreVertIcon />
                                            </IconButton>
                                            <Menu
                                                id="simple-menu"
                                                anchorEl={anchorEl}
                                                keepMounted
                                                open={Boolean(anchorEl)}
                                                onClose={handleClose}
                                            >
                                                <MenuItem onClick={handleClose}>Edit </MenuItem>
                                                <MenuItem onClick={handleClose}>Delete</MenuItem>
                                            </Menu>
                                        </>
                                    }
                                  />

enter image description here

You can tell due to the large shadow behind the Menu, that's not CSS by choice... thats all my menus stacked up from my <Card> component. I know the culprit is having one open close state property. Is there any "quickfix" to this solution? I can't hardcode the states themselves.

Here is the rest of the code where I return my map function that produces <Card>. Please check my <CardHeader>. Note that I have two <CardHeaders>, that's because I have a conditional that dictates which CardHeader to use for the card.

const UserBuckets = (props) => {


    const [anchorEl, setAnchorEl] = React.useState(null);

    const handleClick = (event) => {
      setAnchorEl(event.currentTarget);
    };
  
    const handleClose = () => {
      setAnchorEl(null);
    };


    if (!buckets || buckets.length === 0) return <p>Can not find any buckets, make one below!</p>;
    return (
        <React.Fragment style={{width:"90%"}}>
            <Container style={{width:"90%"}} maxWidth="md" component="main">
                <Grid container spacing={5} alignItems="stretch">
                    {buckets.map((bucket) =>
                    {
                    return (
                        <Grid item key={bucket.id} xs={12} sm={6} md={4} lg={4}>
                            <Card
                            className={classes.root}
                            classes={{ root: state.raised ? classes.cardHovered : "" }}
                            onMouseOver={() => setState({ shadow: 3 })}
                            onMouseOut={() => setState({ shadow: 1 })}
                            zdepth={state.shadow}
                            style={{  height: "100%", borderRadius:"30px"}}
                            >
                            {(!bucket || bucket.stock_list === null) &&
                                <CardHeader className={classes.bucketTitle} classes={{ title: classes.bucketTitle }}
                                        title={
                                            <>
                                            <Link 
                                                color="textPrimary"
                                                href={'dash/' + bucket.slug}
                                                className={classes.link}
                                                style={{ textDecoration: 'none' }}
                                            >
                                                {bucket.name.substr(0, 50)}
                                            </Link>
                                            </>
                                        }
                                        subheader="Add Stocks to get started!"
                                        action={
                                            <>
                                            <IconButton 
                                                onClick={handleClick}
                                                aria-label="settings">
                                                    <MoreVertIcon />
                                                </IconButton>
                                                <Menu
                                                    id="simple-menu"
                                                    anchorEl={anchorEl}
                                                    keepMounted
                                                    open={Boolean(anchorEl)}
                                                    onClose={handleClose}
                                                    style={{boxShadow: 'none'}}
                                                    elevation={0}
                                                >
                                                    <MenuItem onClick={handleClose}>Edit </MenuItem>
                                                    <MenuItem onClick={handleClose}>Delete</MenuItem>
                                                </Menu>
                                            </>
                                        }
                            />}
                            {bucket && bucket.stock_list != null &&
                                <CardHeader className="cardHeaderBucket" 
                                    title={bucket.name.substr(0, 20)}
                                    subheader={bucket.about}
                                    action={
                                        <>
                                        <IconButton 
                                            onClick={handleClick}
                                            aria-label="settings">
                                                <MoreVertIcon />
                                            </IconButton>
                                            <Menu
                                                id="simple-menu"
                                                anchorEl={anchorEl}
                                                keepMounted
                                                open={Boolean(anchorEl)}
                                                onClose={handleClose}
                                            >
                                                <MenuItem onClick={handleClose}>Edit </MenuItem>
                                                <MenuItem onClick={handleClose}>Delete</MenuItem>
                                            </Menu>
                                        </>
                                    }
                                    style={{margin:0}}
                                />}
                                
                                <CardContent className={classes.cardContent}>
                                    {(!bucket || bucket.bucket_pos_neg === null) &&
                                    <p style={{ textAlign: "center" }} >
                                        Your Bucket is empty...
                                    </p>}
                                    {bucket && bucket.bucket_pos_neg != null &&
                                            <div className={classes.bucketText}>
                                    <Grid>
                                    <Typography variant="subtitle1" color="textSecondary">
                                                    {/* {bucket.stock_list.join(",").substr(0, 15)}... */}
                                                    {"Total Stocks: " + bucket.stock_count}
                                    </Typography>
                                    <Typography variant="overline">
                                        Return Donut                
                                    </Typography>
                                    <BucketDoughnutDisplay data={bucket.bucket_pos_neg} />
                                    </Grid>
                                    </div>
                                            }   
                                </CardContent>                              
                            </Card>
                        </Grid>                 
                        );                              
                    })}
                </Grid>
            </Container>
        </React.Fragment>
    );
};

How can I fix my Menu to be unique for each individual card? My one state for my approach is causing major issues. Thank you for the help.


EDIT: Right now the menu is not popping up and no consle errors to work from. I have made the second round of revision. Please see this code

const UserBuckets = (props) => {

    const [anchorEl, setAnchorEl] = React.useState(null);

    const [currentIndex, setCurrentIndex] = useState(0);
    const handleClick = (index) => (event) => {
      setAnchorEl(event.currentTarget);
      setCurrentIndex(index);
    };
    const handleClose = () => {
      setAnchorEl(null);
    };


    const classes = useStyles();

    if (!buckets || buckets.length === 0) return <p>Can not find any buckets, make one below!</p>;
    return (
<React.Fragment>
            <Container style={{width:"90%"}} maxWidth="md" component="main">
                <Grid container spacing={5} alignItems="stretch">
                    {buckets.map((bucket, index) =>
                    {
                    return (
                        <Grid item key={bucket.id} xs={12} sm={6} md={4} lg={4}>
                            <Card
                            className={classes.root}
                            classes={{ root: state.raised ? classes.cardHovered : "" }}
                            onMouseOver={() => setState({ shadow: 3 })}
                            onMouseOut={() => setState({ shadow: 1 })}
                            zdepth={state.shadow}
                            style={{  height: "100%", borderRadius:"30px"}}
                            >
                            {(!bucket || bucket.stock_list === null) &&
                                <CardHeader className={classes.bucketTitle} classes={{ title: classes.bucketTitle }}
                                        title={
                                            <>
                                            <Link 
                                                color="textPrimary"
                                                href={'dash/' + bucket.slug}
                                                className={classes.link}
                                                style={{ textDecoration: 'none' }}
                                            >
                                                {bucket.name.substr(0, 50)}
                                            </Link>
                                            </>
                                        }
                                        subheader="Add Stocks to get started!"
                                        action={
                                            <>
                                            <IconButton 
                                                onOpen={handleClick(index)}
                                                aria-label="settings">
                                                <MoreVertIcon />
                                            </IconButton>
                                                <Menu
                                                    id="simple-menu"
                                                    anchorEl={anchorEl}
                                                    keepMounted
                                                    open={Boolean(anchorEl) && currentIndex}
                                                    onClose={handleClose}
                                                    style={{boxShadow: 'none'}}
                                                    elevation={0}
                                                >
                                                    <MenuItem onClick={handleClick(index)}>Edit </MenuItem>
                                                    <MenuItem onClick={handleClick(index)}>Delete</MenuItem>
                                                </Menu>
                                            </>
                                        }
                            />}
                            {bucket && bucket.stock_list != null &&
                                <CardHeader className="cardHeaderBucket" 
                                    title={bucket.name.substr(0, 20)}
                                    subheader={bucket.about}
                                    action={
                                        <>
                                        <IconButton 
                                             onOpen={handleClick(index)}
                                            aria-label="settings">
                                                <MoreVertIcon />
                                            </IconButton>
                                            <Menu
                                                id="simple-menu"
                                                anchorEl={anchorEl}
                                                keepMounted
                                                open={Boolean(anchorEl) && currentIndex}
                                                onClose={handleClose}
                                            >
                                                <MenuItem onClick={handleClick(index)}>Edit </MenuItem>
                                                <MenuItem onClick={handleClick(index)}>Delete</MenuItem>
                                            </Menu>
                                        </>
                                    }
                                    style={{margin:0}}
                                />}
                                
                                <CardContent className={classes.cardContent}>
                                    {(!bucket || bucket.bucket_pos_neg === null) &&
                                    <p style={{ textAlign: "center" }} >
                                        Your Bucket is empty...
                                    </p>}
                                    {bucket && bucket.bucket_pos_neg != null &&
                                            <div className={classes.bucketText}>
                                    <Grid>
                                    <Typography variant="subtitle1" color="textSecondary">
                                                    {/* {bucket.stock_list.join(",").substr(0, 15)}... */}
                                                    {"Total Stocks: " + bucket.stock_count}
                                    </Typography>
                                    <Typography variant="overline">
                                        Return Donut                
                                    </Typography>
                                    <BucketDoughnutDisplay data={bucket.bucket_pos_neg} />
                                    </Grid>
                                    </div>
                                            }   
                                </CardContent>                              
                            </Card>
                        </Grid>                 
                        );                              
                    })}
                </Grid>
            </Container>
        </React.Fragment>
    );
};

Upvotes: 3

Views: 1105

Answers (2)

Shreedhar patil
Shreedhar patil

Reputation: 1

const Navbar = () => {
  const classes = useStyles();
  const [anchorEl, setAnchorEl] = useState(null);
  const [currentIndex, setCurrentIndex] = useState(0);

  const handleClick = (index) => (e) => {
    setAnchorEl(e.currentTarget);
    setCurrentIndex(index);
    console.log("index", currentIndex);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  const invoiceItem = [
    {
      id: 0,
      title: "New Invoice",
      link: "",
    },
    {
      id: 1,
      title: "Recent Invoices",
      link: "",
    },
    {
      id: 2,
      title: "List All invoices",
      link: "",
    },
  ];

  const companyItem = [
    {
      id: 0,
      title: "New Company",
      link: "",
    },
    {
      id: 1,
      title: "All Company List",
      link: "",
    },
  ];

  return (
    <AppBar
      className={classes.root}
      style={{ background: "#282423" }}
      position="static"
    >
      <Toolbar>
        <Typography variant="h6" className={classes.title}>
          SSENTERPRISES
        </Typography>
        <Tabs textColor="inherit">
          <Tab
            onClick={handleClick(1)}
            aria-controls="invoices"
            label="Invoices"
          />
          <Menu
            id="invoices"
            keepMounted
            anchorEl={anchorEl}
            open={currentIndex === 1 ? Boolean(anchorEl) : null}
            onClose={handleClose}
          >
            {invoiceItem.map((item) => (
              <MenuItem
                key={item.id}
                style={{ fontWeight: item.id === 0 ? "bold" : "normal" }}
                onClick={handleClose}
              >
                {item.title}
              </MenuItem>
            ))}
          </Menu>
          <Tab
            onClick={handleClick(2)}
            aria-controls="company"
            label="Company"
          />
          <Menu
            id="company"
            keepMounted
            anchorEl={anchorEl}
            open={currentIndex === 2 ? Boolean(anchorEl) : null}
            onClose={handleClose}
          >
            {companyItem.map((item) => (
              <MenuItem
                key={item.id}
                style={{ fontWeight: item.id === 0 ? "bold" : "normal" }}
                onClick={handleClose}
              >
                {item.title}
              </MenuItem>
            ))}
          </Menu>
          <Tab label="Material" />
          <Tab label="Reports" />
        </Tabs>
      </Toolbar>
    </AppBar>
  );
};

Upvotes: 0

NearHuscarl
NearHuscarl

Reputation: 81430

Try storing the current index alongside with your open state to identify the current Menu to open:

const [currentIndex, setCurrentIndex] = useState(0);
const handleClick = (index) => (event) => {
  setAnchorEl(event.currentTarget);
  setCurrentIndex(index);
};
buckets.map((bucket, index) => (
  <>
    <Grid>
      <Menu open={Boolean(anchorEl) && currentIndex} {...props} />
    </Grid>
    <IconButton onOpen={handleClick(index)}/>
  </>
))

Upvotes: 3

Related Questions