Mapped data rendering multiple Material-UI Dialog components - javascript

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>

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

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>
))
}
</>
)

Related

Not able to see anything written inside the return component in JSX

I'm new to React and JSX. I'm building a button when clicked opens a dialog box with a table inside it (1x3) which has name, status, & exception error as "strings".
For some reason, when I click the button, I can see the console.log() statement in my console being shown, but nothing shows up as a pop-up dialog box (so, anything and everything inside the return() statement).
const isFailed = () => {
console.log(testCase["test-method"][0].status);
//alert(testCase["test-method"][0].exception);
return(
<Dialog maxWidth="xl" open={open} onClose={() => setOpen(false)}>
<DialogTitle>{testCase.name} Summary</DialogTitle>
<DialogContent>
<Table>
{steps.map((val) => (
<TableRow key={val.name}>
<TableCell>{val.name}</TableCell>
<TableCell>{val.status}</TableCell>
<TableCell>{val.exception}</TableCell>
</TableRow>
))}
</Table>
</DialogContent>
</Dialog>
);
}
EDIT: isFailed function is called by the onClick Button handler
const steps = testCase["test-method"];
return (
<>
<TableRow key={key} style={{ cursor: "pointer" }} onClick={() => setOpen(true)}>
<TableCell>{testCase.name}</TableCell>
<TableCell>{testCase.duration}</TableCell>
<StatusCell fail={testCase.fail} skip={testCase.skip} />
<TableCell>{(typeof(testCase["test-method"][0].exception) == typeof("string")) ? <div> <Button onClick = {isFailed} color = "primary">View Error</Button> </div>: null}</TableCell>
</TableRow>
</>
);
If someone can help me with this, it will be awesome. Thanks.
You can't use return method inside a onClick function.
You might need to maintain a separate state to handle the onclick function.
const steps = testCase["test-method"];
return (
<>
<TableRow key={key} style={{ cursor: "pointer" }} onClick={()
=> setOpen(true)}>
<TableCell>{testCase.name}</TableCell>
<TableCell>{testCase.duration}</TableCell>
<StatusCell fail={testCase.fail} skip={testCase.skip} />
<TableCell>{(typeof(testCase["test-method"][0].exception) == typeof("string")) ? <div> <Button onClick = {isFailed} color = "primary">View Error</Button> </div>: null}</TableCell>
</TableRow>
</>
{isClicked &&
<Dialog maxWidth="xl" open={open} onClose={() => setOpen(false)}>
<DialogTitle>{testCase.name} Summary</DialogTitle>
<DialogContent>
<Table>
{steps.map((val) => (
<TableRow key={val.name}>
<TableCell>{val.name}</TableCell>
<TableCell>{val.status}</TableCell>
<TableCell>{val.exception}</TableCell>
</TableRow>
))}
</Table>
</DialogContent>
</Dialog>
}
);
On onClick function just set the boolean value of isClicked function.
const [isClicked, setIsClicked] = React.useState(false);
const isFailed = () => {
setIsClicked(true);
}

Material ui Dialog onClick deletes only last index

So I've created a get request, which shows all the exercises and the options to edit (which will redirect to another url) and delete. I wanted to use material ui's Dialog, so there will be a confirmation before deleting. The problem is that, say I have 3 exercises and want to delete exercise with index 1, when click the on delete button it appears the last index in the element i.e the last exercise, although I want to delete the second. How can I fix that?
And also can I move Dialog in another file for more clear coding?
Exercises.js
const Exercises = () => {
const { categories, deletedCategories, error, isLoading, exercises, open, handleClickOpen, handleClose, deleteExercise } = useFormExercises();
const [showCategories, setShowCategories] = useState(false);
if (isLoading) {
return <div>Loading...</div>
}
if (error) {
return <div>There was an error: {error}</div>
}
return (
<Grid container className='content' >
<Card className='exerciseCard'>
<h1>Exercises</h1>
<FormControlLabel sx={{ width:'500px'}}
label="Show Categories"
control={
<Checkbox
sx={{ml:2}}
checked={showCategories}
onChange={(event) => setShowCategories(event.target.checked)}
/>
}
/>
<Button sx={{pr:6}} variant='fab' href={('/exercises/add')}>
<Add />
</Button>
<Divider />
<Grid className="exerciseContent" >
{exercises.map(exercise => (
<Card>
<tr key={exercise.id}>
<List>
<ListItem
key={exercise.id}
secondaryAction={
<IconButton onClick={handleClickOpen}>
<Delete />
</IconButton>
}>
<Button
sx={{mr:4}}
href={(`/exercises/edit/${exercise.id}`)} >
<Edit />
</Button>
<ListItemText sx={{justifyContent:'center', width:'400px'}}
primary={exercise.exercise_name}
secondary={showCategories ? exercise["exerciseCategories.category_name"] : null}
/>
</ListItem>
<Dialog
open={open}
onClose={handleClose}
style={{ borderColor: 'red' }}
>
<Box sx={{ borderTop: 3, color: 'red' }}>
<DialogTitle sx={{ color: 'black', backgroundColor: 'gainsboro', pl: 11 }}>
Delete Exercise
</DialogTitle>
<DialogContent>
<DialogContentText color='black' >
<Warning fontSize='large' color='error' id='warning' />
Are you sure you want to delete the exercise: {exercise.exercise_name} ?
</DialogContentText>
</DialogContent>
<DialogActions >
<Button variant='contained' onClick={() => deleteExercise(exercise.id)} autoFocus>
Yes
</Button>
<Button variant='outlined' onClick={handleClose}>No</Button>
</DialogActions>
</Box>
</Dialog>
</List>
</tr>
</Card>
))}
</Grid>
</Card>
</Grid>
)
}
export default Exercises
My delete function
useFormExercises.js
...
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
}
const deleteExercise = async (id) => {
try {
await fetch(`${BASE_URL}/exercises/${id}`, {
method: "DELETE",
}).then(response => {
setExercises(exercises.filter(exercise => exercise.id !== id))
return response.json()
})
} catch (error) {
console.log(error)
}
}
And if another thing seems of, please share your opinion!
Thanks in advance!

How to use a Dialog box inside a Card Action Button Group

I'm trying to figure out if it's possible to have a Dialog box as a button inside a Button Group, inside a CardActions component in Material UI.
When I use a regular Button instead of a Dialog, the 3 buttons in the CardActions are evenly justified across the width of the component - which is what I'm trying to preserve, whilst making the onClick action of the button, a pop up dialog box.
I have:
<CardActions>
<ButtonGroup
orientation="horizontal"
color="secondary"
aria-label="vertical contained primary button group"
variant="text"
fullWidth
>
<1 />
<2 />
<3 Services</Button>
</ButtonGroup>
</CardActions>
Each of 1, 2 and 3 are files which have:
import React from 'react';
import Button from '#material-ui/core/Button';
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 LaunchIcon from '#material-ui/icons/Launch';
export default function ScrollDialog() {
const [open, setOpen] = React.useState(false);
const [scroll, setScroll] = React.useState('paper');
const handleClickOpen = (scrollType) => () => {
setOpen(true);
setScroll(scrollType);
};
const handleClose = () => {
setOpen(false);
};
const descriptionElementRef = React.useRef(null);
React.useEffect(() => {
if (open) {
const { current: descriptionElement } = descriptionElementRef;
if (descriptionElement !== null) {
descriptionElement.focus();
}
}
}, [open]);
return (
<div>
<Button color="secondary" onClick={handleClickOpen('paper')}>
1
</Button>
<Dialog
open={open}
onClose={handleClose}
scroll={scroll}
aria-labelledby="scroll-dialog-title"
aria-describedby="scroll-dialog-description"
maxWidth="sm"
>
<DialogTitle id="scroll-dialog-title">Sampling Techniques</DialogTitle>
<DialogContent dividers={scroll === 'paper'}>
<DialogContentText
id="scroll-dialog-description"
ref={descriptionElementRef}
tabIndex={-1}
>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</div>
);
}
When I try this, the Dialog pops up like a modal, but the buttons do not preserve the justification styling of the ButtonGroup (that worked when I just used buttons inside that Card Action component).
Is it possible to have a Dialog box inside a Card Action?
The .MuiButtonGroup-root is an inline-flex container. Use width: "100%" and justifyContent: "space-evenly" so that
the 3 buttons in the CardActions are evenly justified across the width
of the component
const useStyles = makeStyles({
customBtnGroup: {
width: "100%",
justifyContent: "space-evenly"
}
});
<ButtonGroup classes={{ root: classes.customBtnGroup }}>

Material-ui Dialog: manipulating the Escape button and mouse clicking outside of the dialog

I'm using material-ui's dialog: when a user presses "sign out" button, a dialog opens and there appears "yes" or "no" buttons asking whether the user realy wants to sign out or not.
When the user clicks the "escape" button or just clicks outside of the window, it signs him out - as if he pressed "yes". How can I control this? I want those to activate the "no" button.
Thanks!
Ciao, you could use a Confirmation Dialog variant of Mui Dialog. In this way you could force the user to click button Yes or No in your dialog.
Here I made a codesandbox example (inspired by Mui Dialog page)
Basically I made a simple Button with a component that calls Dialog:
return (
<div className={classes.root}>
<Button onClick={handleClickListItem}>sign out</Button>
<ConfirmationDialogRaw
classes={{
paper: classes.paper
}}
keepMounted
open={open}
onClose={handleClose}
/>
</div>
);
The ConfirmationDialogRaw component:
function ConfirmationDialogRaw(props) {
const { onClose, open, ...other } = props;
const handleCancel = () => {
// here just close the confirmation dialog
console.log("stay in");
onClose();
};
const handleOk = () => {
// here manage signout
console.log("sing out");
onClose();
};
return (
<Dialog
disableBackdropClick
disableEscapeKeyDown
maxWidth="xs"
aria-labelledby="confirmation-dialog-title"
open={open}
{...other}
>
<DialogTitle id="confirmation-dialog-title">Sing Out</DialogTitle>
<DialogContent dividers>
<Typography>Are you sure you want to exit?</Typography>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel} color="primary">
No
</Button>
<Button onClick={handleOk} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
);
}
The magic is done by props disableBackdropClick and disableEscapeKeyDown on Dialog component.

Trying to open and close a Modal in different components

I am using a Material UI Dialog Form between two components. In the parent component that is Productos.js, I open the component and the child component EditarProductos.js receives the open state from its parent.
I am having trouble because I can only open the dialog once and then I can't open it again.
Below is the code from Productos.js (parent component)
//Code...
import EditarProductos from './EditarProductos';
//Code...
function Productos(props) {
const [open, setOpen] = React.useState(false);
//Code...
function editar(producto){
//Code...
setOpen(true);
}
//Code...
return (
<div>
{id && nombre && precioCompra && precioVenta && cantidad && open &&
<EditarProductos productoEdit={id} productoNombre={nombre} productoPrecioCompra ={precioCompra}
productoPrecioVenta = {precioVenta} productoCantidad = {cantidad} isOpen = {open}/>
}
<br />
//Code...
<br />
<Table className={classes.table}>
<TableHead>
<TableRow>
//Code...
</TableRow>
</TableHead>
<TableBody>
{productos.map((producto) =>
<TableRow className="data-row">
<StyledTableCell>{producto.id
}</StyledTableCell>
<StyledTableCell>{producto.nombre}</StyledTableCell>
<StyledTableCell>{producto.precio_compra}</StyledTableCell>
<StyledTableCell>{producto.precio_venta}</StyledTableCell>
<StyledTableCell>{producto.cantidad}</StyledTableCell>
<StyledTableCell>{producto.categorias_id}</StyledTableCell>
<StyledTableCell>
<Button variant="outlined" onClick={() => editar(producto)}>
<EditIcon />
</Button>
<Button variant="outlined" onClick={() => eliminar(producto)} ><DeleteIcon /> </Button>
</StyledTableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
export default Productos;
Below is EditarProdutos.js (child component)
import {Button, TextField, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from '#material-ui/core';
function EditarProductos(props){
var abierto = props.isOpen;
const [open, setOpen] = React.useState(abierto);
const handleClose = () => {
abierto = false;
};
//Code...
return (
<div>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Editar</DialogTitle>
//Code...
<Button onClick={handleClose} color="primary">
Cancelar
</Button>
//Code...
</DialogActions>
</Dialog>
</div>
);
}
export default EditarProductos;
Thank you so much!
The problem with your code is the internal state that EditarProductos component holds.
The idiomatic and easy solution for this would be, that you pass down the isOpen flag and the handleClose method from the parent, the EditarProductos wouldn't have an internal state, just use the props:
function EditarProductos(props){
return (
<div>
<Dialog open={props.isOpen} onClose={props.handleClose}>
<DialogTitle>Editar</DialogTitle>
<DialogActions>
<Button onClick={props.handleClose} color="primary">
Cancelar
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default EditarProductos;
And in your Productos component you need to define a handleClose method as well (with setOpen(false)) and you need to pass down that too with isOpen

Categories