Trying to open and close a Modal in different components - javascript

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

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

How to open Modal from another component in hooks?

In a React project, I have the requirement of opening Modal from another component. I found suggested questions from StackOverflow but, not yet convinced. As I need to make the Modal component reusable across all components. See the code below for reference
Homepage.js
const HomePage = () => {
return (
<>
<button onClick={() => setLoginModalShow(true)}>Open Modal</button>
</>
);
};
I created the below file to export other files too, and make it useful in other components
commonLogin.js
import LoginModal from "./LoginModal";
export const setLoginModalShow = (props) => {
console.log("PROPS", props);
return <LoginModal showModal={props} />;
};
And here is the Modal component
const LoginModal = (props) => {
const [loginModalShow, setLoginModalShow] = useState(props.showModal);
console.log("PROPS in MODAL", props);
return (
<>
<Modal
show={loginModalShow}
onHide={setLoginModalShow}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">Logout</Modal.Title>
</Modal.Header>
<Modal.Body>
<h4>Are you sure to Logout?</h4>
</Modal.Body>
<Modal.Footer>
<Button onClick={() => setLoginModalShow(false)}>Cancel</Button>
</Modal.Footer>
</Modal>
</>
);
};
Please refer to the code sandbox link: https://codesandbox.io/s/nameless-cdn-70zuq.
After you visit the page click on the 'Open Modal' button
You want to control the show/hide from the parent component
const HomePage = () => {
const [showLogin, setShowLogin] = useState(false);
return (
<>
<button onClick={() => setShowLogin(true)}>Open Modal</button>
<LoginModal show={showLogin} close={() => setShowLogin(false)} />
</>
);
};
<Modal
show={props.show}
cancel={props.close}
...
Here is a working example:
https://codesandbox.io/s/nice-euclid-d0dw0

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

Opening a Material-UI dialog modal from a Header parent component

I have a header component separate from a Register dialog modal component. So like a parent an child components. I want to call the Register dialog (child) from the headerlink component (parent)
Here's my headerlinks component:
...
import Register from "../Register";
....
export default function HeaderLinks(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<List className={classes.list}>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
onClick={handleClickOpen}
>
Register
</Button>
</ListItem>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
>
Log In
</Button>
</ListItem>
</List>
);
<Register open={handleClickOpen} onClose={handleClose} />;
}
The Register dialog component was simply copied from Material-UI's documentation and removed the button.
export default function Register() {
const [open, setOpen] = React.useState(false);
const classes = useStyles();
const theme = useTheme();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Register
</DialogTitle>
<DialogContent dividers>
...content...
</DialogContent>
</Dialog>
</div>
);
}
When I click the Register button on the headerLink, nothing happens. Not sure what else I'm missing because there are no errors.
On your HeaderLinks, you've already returned a value on the function so the rest of source code below that will not execute.
return (
<List className={classes.list}>
...
</List>
);
<Register open={handleClickOpen} onClose={handleClose} />; // <-- code will not be reached
So just move that inside of the return statement and close them on a single tag because adjacent JSX elements must be wrapped in an enclosing tag. Pass down props to Register, it does not need an internal state since its open prop (according to your design it seems) is to be controlled by HeaderLinks component
export default function HeaderLinks() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<List className={classes.list}>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
onClick={handleClickOpen}
>
Register
</Button>
</ListItem>
<ListItem className={classes.listItem}>
<Button color="transparent" className={classes.navLink}>
Log In
</Button>
</ListItem>
</List>
<Register open={open} handleClose={handleClose} />
</>
);
}
function Register({ open, handleClose }) {
const classes = useStyles();
const theme = useTheme();
return (
<div>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Register
</DialogTitle>
<DialogContent dividers>...content...</DialogContent>
</Dialog>
</div>
);
}

Open Dialog From Other Page In React

I'm opening a dialog component when i click the DELETE function button on my users list. When i click it, should show the dialog component. My problem is why i can't open it. I'm using redux to pass data to it.
Pls see this codesandbox link
CLICK HERE
import { dialogConstants } from "../constants";
export const initialState = {
title: null,
details: null,
isOpen: null
};
const dialogReducer = (state = initialState, action) => {
console.log(action.payload);
switch (action.type) {
case dialogConstants.SET_DIALOG_DETAILS:
return {
...state,
isOpen: action.payload.isOpen,
title: action.payload.title,
details: action.payload.details
};
default:
return state;
}
};
export default dialogReducer;
You are not importing Dialogs in user.js. So when you click button your dialog will not open. Try this:
In user.js:
...
import DeleteDialog from "./dialog";
import { useDispatch } from "react-redux";
import { deleteUser } from "./actions";
export default function User() {
const dispatch = useDispatch();
const [selectedUser, setSelectedUser] = React.useState({});
const [open, setDialogOpen] = React.useState(false);
const handleOnDelete = user => {
setSelectedUser(user);
setDialogOpen(true);
};
const handleOnAgree = () => {
// do action to handle on agree deleting an user
dispatch(deleteUser({ title: "Delete User", details: selectedUser }));
setDialogOpen(false);
};
return (
<div>
<Paper>
<TableContainer>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell>Email Address</TableCell>
<TableCell>Actions</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>JJJ</TableCell>
<TableCell>BBB</TableCell>
<TableCell>enfoie</TableCell>
<TableCell>
<Button variant="contained">Edit</Button>
<Button
variant="contained"
onClick={() => handleOnDelete({ id: 1, name: "JJJ" })}
>
Delete
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Paper>
<DeleteDialog
user={selectedUser}
open={open}
onAgree={handleOnAgree}
onDisagree={() => setDialogOpen(false)}
/>
</div>
);
}
In dialog.js:
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 DialogTitle from "#material-ui/core/DialogTitle";
import Slide from "#material-ui/core/Slide";
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
const DeleteDialog = ({ user, open, onAgree, onDisagree }) => {
return (
<div>
<Dialog
open={open}
TransitionComponent={Transition}
keepMounted
onClose={onDisagree}
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
>
<DialogTitle id="alert-dialog-slide-title">
<span style={{ fontWeight: "bold" }}>
{" "}
User: {user.name} - {user.id}
</span>
</DialogTitle>
<DialogActions>
<Button variant="contained" size="small" onClick={onDisagree}>
Cancel
</Button>
<Button variant="contained" size="small" onClick={onAgree}>
Confirm
</Button>
</DialogActions>
</Dialog>
</div>
);
};
export default DeleteDialog;

Categories