DJ2
DJ2

Reputation: 1741

Mapped data rendering multiple Material-UI Dialog components

I'm displaying a list of users in a table. Each row has a cell which contains a button to delete the user. When the delete button is clicked I'm rendering a Material-UI <Dialog /> component to confirm.

The issue is that the map is causing 3 dialogs to be rendered and the last one is the one on top. So If I have 3 users in the array, 3 dialogs are rendered but you can only interact with the 3rd dialog (it blocks the other 2 from view).

The issue is similar to this one.

Is there a way to control which dialog is opened?

handleDeleteUser is an async function making a request so I need to pass id and links to that method.

I have a simplified codesandbox here. If you try to delete user 1 or user 2, you can see that only the id and link for user 3 appear in the console.

const handleDeleteUser = (id, links) => {
  // this always prints the id and links for the last user in the table
  console.log("deleting user -->", id);
  console.log("user delete URL", links.self);
  setOpen(false);
};

<Table.Body>
  {userList && userList.length >= 1 ? (
    userList.map(
      ({ id, attributes, links, i }, index) => {
        return (
          <Table.Row key={index}>
            <Table.Cell textAlign="center">
              <Button
                circular
                icon
                size="mini"
                color="red"
                type="button"
                onClick={handleClickOpen}
              >
                <Icon name="delete" />
              </Button>
              <Dialog
                open={open}
                onClose={handleClose}
                aria-labelledby="alert-dialog-title"
                aria-describedby="alert-dialog-description"
              >
              <DialogTitle id="alert-dialog-title">
                {"Confirm User Delete"}
              </DialogTitle>
              <DialogContent>
                <DialogContentText id="alert-dialog-description">
                  {`Are you sure you want to delete this user?`}
                </DialogContentText>
              </DialogContent>
              <DialogActions>
                <MUIButton
                  type="button"
                  onClick={handleDeleteUser.bind(
                    this,
                    id,
                    links
                  )}
                  color="primary"
                >
                  Yes
                </MUIButton>
                <MUIButton
                  type="button"
                  onClick={handleClose}
                  color="primary"
                  autoFocus
                 >
                   Cancel
                </MUIButton>
              </DialogActions>
            </Dialog>

Upvotes: 3

Views: 1876

Answers (2)

Kubilay Şimşek
Kubilay Şimşek

Reputation: 156

Why did you render dialog in every loop? I think,you need to once define.Because your "open" state for dialog same every user and triggered whichever is clicked on.You can try;

const [ selectedUser,setSelectedUser ] = useState()
const [ open,setOpen ] = useState(false)

const handleDeleteUser = (id,links) => {
    // this always prints the id and links for the last user in the table
    console.log("deleting user -->", id);
    console.log("user delete URL", links.self);
    setOpen(false);
};

return(
    <>
        <Dialog
            open={open}
            onClose={handleClose}
            aria-labelledby="alert-dialog-title"
            aria-describedby="alert-dialog-description"
        >
            <DialogTitle id="alert-dialog-title">
            {"Confirm User Delete"}
            </DialogTitle>
            <DialogContent>
                <DialogContentText id="alert-dialog-description">
                    {`Are you sure you want to delete ${selectedUser.name} user?`}
                </DialogContentText>
            </DialogContent>
            <DialogActions>
                <MUIButton
                type="button"
                onClick={() => handleDeleteUser(selectedUser.id,selectedUser.links)}
                color="primary"
                >
                Yes
                </MUIButton>
                <MUIButton
                type="button"
                onClick={handleClose}
                color="primary"
                autoFocus
                >
                Cancel
                </MUIButton>
            </DialogActions>
        </Dialog>
        {
            users.map((user,index) => (
                <TableRow style={{backgroundColor:'green'}} key={index}>
                    <TableCell style={{color:'#fff'}}>{user.name}</TableCell>
                    <Button onClick={() => {
                        selectedUser(user),
                        setOpen(true)
                    }} >Delete me!</Button>
                </TableRow>     
            ))
        }
    </>
)

Upvotes: 0

IAMSTR
IAMSTR

Reputation: 684

The problem you have is when the button to trigger the modal is pressed it would set the state open to true and all the Dialog shared this

what you do is move the Dialog component to a different file and import it from there and pass the click handlers and state down as props.

here is my solution

step 1

Move the button that triggers the modal open and the dialog component into a different file

import React, { useState } from "react";

import { Button, Icon } from "semantic-ui-react";

import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";

import MUIButton from "@material-ui/core/Button";

const CustomDialog = (props) => {
  const [open, setOpen] = useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <>
      <Button
        circular
        icon
        size="mini"
        color="red"
        type="button"
        onClick={() => handleClickOpen()}
      >
        <Icon name="delete" />
      </Button>

      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
      >
        <DialogTitle id="alert-dialog-title">
          {"Confirm User Delete"}
        </DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-description">
            {`Are you sure you want to delete this user: ${props.id}?`}
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <MUIButton
            type="button"
            onClick={() => {
              props.handleDeleteUser(props.id, props.links);
              setOpen(false);
            }}
            color="primary"
          >
            Yes
          </MUIButton>
          <MUIButton
            type="button"
            onClick={handleClose}
            color="primary"
            autoFocus
          >
            Cancel
          </MUIButton>
        </DialogActions>
      </Dialog>
    </>
  );
};
export default CustomDialog;

step 2

I refactored your table component like this

                    <React.Fragment key={index}>
                        <Table.Row key={index}>
                          <Table.Cell textAlign="center">
                            <CustomDialog
                              id={id}
                              links={links}
                              handleDeleteUser={handleDeleteUser}
                            />
                          </Table.Cell>
                          <Table.Cell>{id}</Table.Cell>
                        </Table.Row>
                      </React.Fragment>

as you see I have passed the id and links and handleDeleteUser as props , by doing so you make your dialog have its own state rather than sharing the state hence you were not getting the result you wanted

you can preview it here codesandbox

Tip

It's better having every component on a different file and its way cleaner, you can easily debug problems that would arise

Upvotes: 5

Related Questions