Home > OS >  How can I make onClick events work with specific elements in a .map() function?
How can I make onClick events work with specific elements in a .map() function?

Time:09-27

I'm trying to make a list of users inside a chat. Each user is supposed to have a button opening a menu, allowing admins to ban that specific user. I'm mapping an array of users to get the list, but the issue is that whenever I try to click on the 'ban' button, the user data seems to always be for the last user in the array, instead of the user on which I clicked. Here's the code for the entire component:

const UserList: React.FC<{channel: string}> = ({channel}) => {
    const [channelMembers, setChannelMembers] = useState<any[] | null>(null)
    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
    const [isAdmin, setIsAdmin] = useState(false)
    const navigate = useNavigate()
    const open = Boolean(anchorEl)
    const baseUrl = useContext(UrlContext)

    useEffect(() => {
        axios.get(baseUrl   `chat/${channel}/users`, {withCredentials: true}).then((response) => {
            setChannelMembers(response.data)
        })
    }, [baseUrl, channel, anchorEl])

    useEffect(() => {
        axios.get(baseUrl   `users/me`, {withCredentials: true}).then((response) => {
            axios.get(baseUrl   `chat/${channel}/admins`, {withCredentials: true}).then((resp2) => {
                if (resp2.data.includes(response.data.id)) {
                    setIsAdmin(true)
                }
            }).catch((error) => {
                console.log(error)
            })
        }).catch((error) => {
            console.log(error)
        })
    }, [baseUrl, anchorEl])

    const handleClick = (event: React.MouseEvent<HTMLElement>) => {
        setAnchorEl(event.currentTarget)
    }

    const handleClose = () => {
        setAnchorEl(null)
    }
    
    const handleBan = (user: any) => () => {
        let banUser = {...user}

        console.log(banUser)
        axios.post(baseUrl   `chat/${channel}/ban`, banUser, {withCredentials: true}).catch((error) => {
            console.log(error)
        })
        handleClose()
    }

    return (
        <>
        <Drawer
            sx={{
                width: drawerWidth,
                flexShrink: 0,
                '& .MuiDrawer-paper': {
                    width: drawerWidth,
                    boxSizing: 'border-box',
                },
            }}
            variant="permanent"
            anchor="right"
        >
            <Toolbar />
            <List>
                {channelMembers?.map((user: any, index: number) => {

                    return (
                        <Fragment key={index}>
                            <ListItem>
                                <ListItemText primary={user.name} />
                                <ListItemIcon>
                                    {(user.status === Status.ONLINE) ? <CircleIcon style={{color: "green"}} fontSize="small" /> : <RadioButtonUncheckedIcon style={{color: "grey"}} fontSize="small" />}
                                </ListItemIcon>
                                <IconButton
                                    aria-label="more"
                                    id="long-button"
                                    aria-controls={open ? 'long-menu' : undefined}
                                    aria-expanded={open ? 'true' : undefined}
                                    aria-haspopup="true"
                                    onClick={handleClick}
                                >
                                    <MoreVertIcon />
                                </IconButton>
                                <Menu
                                    id="long-menu"
                                    MenuListProps={{
                                        'aria-labelledby': 'long-button'
                                    }}
                                    anchorEl={anchorEl}
                                    open={open}
                                    onClose={handleClose}
                                >
                                    {isAdmin &&
                                    [<MenuItem onClick={handleClose}>
                                        <Typography>
                                            Make admin
                                        </Typography>
                                    </MenuItem>,
                                    <MenuItem onClick={handleClose}>
                                        <Typography>
                                            Mute
                                        </Typography>
                                    </MenuItem>,
                                    <MenuItem onClick={handleClose}>
                                        <Typography>
                                            Kick
                                        </Typography>
                                    </MenuItem>,
                                    <MenuItem onClick={handleBan(user)}>
                                        <Typography>
                                            Ban
                                        </Typography>
                                    </MenuItem>]}
                                    <MenuItem onClick={handleClose}>
                                        <Typography>
                                            View profile
                                        </Typography>
                                    </MenuItem>
                                    <MenuItem onClick={handleClose}>
                                        <Typography>
                                            Invite to play
                                        </Typography>
                                    </MenuItem>
                                </Menu>
                            </ListItem>
                            <hr />
                        </Fragment>
                    )
}               )}
            </List>
        </Drawer>
        </>
    )
}

CodePudding user response:

You're rendering multiple menus in your map and since you're using only one state when opening one menu it sets open to true which means all menus are open because of this prop open={open}.

You should try to render only one Menu and possibly store in a state the selected user. There's probably a better way to do this but it should solve your problem.

CodePudding user response:

You're calling the ban handleBan(user) instead of () => handleBan(user), function instead and using the return value of it.

Try this code

const UserList: React.FC<{ channel: string }> = ({ channel }) => {
  const [channelMembers, setChannelMembers] = useState<any[] | null>(null);
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
  const [isAdmin, setIsAdmin] = useState(false);
  const navigate = useNavigate();
  const open = Boolean(anchorEl);
  const baseUrl = useContext(UrlContext);

  useEffect(() => {
    axios
      .get(baseUrl   `chat/${channel}/users`, { withCredentials: true })
      .then((response) => {
        setChannelMembers(response.data);
      });
  }, [baseUrl, channel, anchorEl]);

  useEffect(() => {
    axios
      .get(baseUrl   `users/me`, { withCredentials: true })
      .then((response) => {
        axios
          .get(baseUrl   `chat/${channel}/admins`, { withCredentials: true })
          .then((resp2) => {
            if (resp2.data.includes(response.data.id)) {
              setIsAdmin(true);
            }
          })
          .catch((error) => {
            console.log(error);
          });
      })
      .catch((error) => {
        console.log(error);
      });
  }, [baseUrl, anchorEl]);

  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };

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

  const handleBan = (user: any) => () => {
    let banUser = { ...user };

    console.log(banUser);
    axios
      .post(baseUrl   `chat/${channel}/ban`, banUser, { withCredentials: true })
      .catch((error) => {
        console.log(error);
      });
    handleClose();
  };

  return (
    <>
      <Drawer
        sx={{
          width: drawerWidth,
          flexShrink: 0,
          "& .MuiDrawer-paper": {
            width: drawerWidth,
            boxSizing: "border-box"
          }
        }}
        variant="permanent"
        anchor="right"
      >
        <Toolbar />
        <List>
          {channelMembers?.map((user: any, index: number) => {
            return (
              <Fragment key={index}>
                <ListItem>
                  <ListItemText primary={user.name} />
                  <ListItemIcon>
                    {user.status === Status.ONLINE ? (
                      <CircleIcon style={{ color: "green" }} fontSize="small" />
                    ) : (
                      <RadioButtonUncheckedIcon
                        style={{ color: "grey" }}
                        fontSize="small"
                      />
                    )}
                  </ListItemIcon>
                  <IconButton
                    aria-label="more"
                    id="long-button"
                    aria-controls={open ? "long-menu" : undefined}
                    aria-expanded={open ? "true" : undefined}
                    aria-haspopup="true"
                    onClick={handleClick}
                  >
                    <MoreVertIcon />
                  </IconButton>
                  <Menu
                    id="long-menu"
                    MenuListProps={{
                      "aria-labelledby": "long-button"
                    }}
                    anchorEl={anchorEl}
                    open={open}
                    onClose={handleClose}
                  >
                    {isAdmin && [
                      <MenuItem onClick={handleClose}>
                        <Typography>Make admin</Typography>
                      </MenuItem>,
                      <MenuItem onClick={handleClose}>
                        <Typography>Mute</Typography>
                      </MenuItem>,
                      <MenuItem onClick={handleClose}>
                        <Typography>Kick</Typography>
                      </MenuItem>,
                      <MenuItem onClick={() => handleBan(user)}>
                        <Typography>Ban</Typography>
                      </MenuItem>
                    ]}
                    <MenuItem onClick={handleClose}>
                      <Typography>View profile</Typography>
                    </MenuItem>
                    <MenuItem onClick={handleClose}>
                      <Typography>Invite to play</Typography>
                    </MenuItem>
                  </Menu>
                </ListItem>
                <hr />
              </Fragment>
            );
          })}
        </List>
      </Drawer>
    </>
  );
};

CodePudding user response:

Instead of doing this:
onClick={handleBan(user)}

Pass an anonymous function to your on click, so you can pass the correct user like this:
onClick={() => handleBan(user)}

Here's a code sandbox showing how the 2 approaches differ.

  • Related