Good morning everyone,
I am currently losing my calm, on the DIALOG component from Material UI.
At first, I would like to implement a simple DIALOG component apparition when I click on a bin Icon (from MUI library)
So for here is what I've done :
Initiating an open state :
const [open, setOpen] = useState(false)
Implement handleOpen and handleClose methods :
const handleClickOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
Implement my bin Icon in a TableCell :
<TableCell>
<DeleteIcon onClick={() => {handleDeleteUser()}}/>
<EditIcon onClick={handleUpdateUser}/>
</TableCell>
Pass a Method handleDeleteUser to handle the trigger of the DIALOG box
And then define my handleDeleteUser which is supposed to pop the component and open a Dialog component
Here is the method :
const handleDeleteUser = () => {
handleClickOpen()
console.log(open)
return(
<Dialog
open={open}
onClose={handleClose}
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Êtes vous sur de vouloir supprimer cet utilisateur ?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Oui
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Non
</Button>
</DialogActions>
</Dialog>
)
}
My issue is the following :
I am able to enter the handleDeleteUser method and I am able to switch the open state from false to true.
Unfortunately, I am not able to return the JSX and so to pop the dialog Box.
As I am a beginner in React and it's my first project using MUI, I am sure it's due to a lack of comprehension of how to use Dialog.
I hope you'll be able to help me!
Many thanks!
Don't return jsx from the handleDeleteUser method. In fact you don't need this method.
<TableCell>
<DeleteIcon onClick={() => handleClickOpen()}/>
<EditIcon onClick={handleUpdateUser}/>
</TableCell>
Now assign the jsx to a variable
const DialogComponent = (
<Dialog
open={open}
onClose={handleClose}
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Êtes vous sur de vouloir supprimer cet utilisateur ?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Oui
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Non
</Button>
</DialogActions>
</Dialog>
);
And use this variable directly inside the render/return method of the parent component function. The variable open will automatically hide the dialog and open the dialog for you. You don't need to return it from the function.
return (
{DialogCOmponent}
)
The <Dialog> template should be present in the render function of the component from the begening. Returning the JSX from the handleDeleteUser function wouldn't help as it won't append the Dialog component to the DOM.
You just need to execute the handleClickOpen function from the handleDeleteUser funtion. And the Dialog template should already be present in the render part to render the dialog.
You may refer to the following sample code-sandbox that uses Dialog: https://codesandbox.io/s/jl06wm10q5
Related
I have two files: Modal.js and Users.js. Users.js contains a table that has an API get query mapped to it, in the final column of the table is a a dropdown for each row that contains three buttons; View, Edit and Delete. I would like the Delete button to trigger the Modal containing a message that roughly reads "Are you sure you wish to delete the user?".
I am struggling to get the Modal to trigger in the onClick event of the Delete component in the Users.js file, I'll attach the code for the two files below.
Modal.js (I have not edited any of the content in the modal yet)
import {
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CModalFooter,
} from "#coreui/react";
const Modal = ({ visible, setVisible }) => {
return (
<>
<CModal visible={visible} onClose={() => setVisible(false)}>
<CModalHeader onClose={() => setVisible(false)}>
<CModalTitle>Modal title</CModalTitle>
</CModalHeader>
<CModalBody>Woohoo, you're reading this text in a modal!</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={() => setVisible(false)}>
Close
</CButton>
<CButton color="primary">Save changes</CButton>
</CModalFooter>
</CModal>
</>
);
};
export default Modal;
Users.js
<CTableRow v-for="item in tableItems" key={rows.userID}>
<CTableDataCell className="text-center">{rows.userID}</CTableDataCell>
<CTableDataCell>{rows.firstName}</CTableDataCell>
<CTableDataCell>
<div>{rows.lastName}</div>
</CTableDataCell>
<CTableDataCell className="column-overflow">{rows.email}</CTableDataCell>
<CTableDataCell>{rows.role}</CTableDataCell>
<CTableDataCell>{rows.createdAt}</CTableDataCell>
<CTableDataCell>{rows.updatedAt}</CTableDataCell>
<CTableDataCell>
<strong>{rows.lastLogin}</strong>
</CTableDataCell>
<CTableDataCell>
<CDropdown>
<CDropdownToggle color="transparent"></CDropdownToggle>
<CDropdownMenu>
<CDropdownItem className="dropdown-item pointer">View</CDropdownItem>
<CDropdownItem className="dropdown-item pointer">Edit</CDropdownItem>
<CDropdownItem
className="dropdown-item text-danger pointer"
onClick={() => Modal()} <- Issue here
>
Delete
</CDropdownItem>
</CDropdownMenu>
</CDropdown>
</CTableDataCell>
</CTableRow>;
I would appreciate any assistance with this. Let me know if I can give any other code (I have reduced the Users.js file as it is just over 160 lines long and I don't want to clutter, except for the row so you can get an idea of where the Delete button lives).
Thanks in advance!
The Modal component needs to be in the page with a visible prop controlled by the parent (and not called as a function). The CoreUI examples for CModal are a bit misleading for your use-case. I suggest using useState in your parent Users component and passing a setter callback to the Modal for the close event.
For example:
Users.js
const Users = () => {
const [visible, setVisible] = React.useState(false);
const [selectedUser, setSelectedUser] = React.useState(null);
const rows = [
{ userID: "1", firstName: "Cameron", lastName: "E" },
{ userID: "2", firstName: "Steve", lastName: "G" }
];
return (
<>
<CTable>
{rows.map((row) => {
return (
<CTableRow v-for="item in tableItems" key={row.userID}>
<CTableDataCell className="text-center">
{row.userID}
</CTableDataCell>
<CTableDataCell>{row.firstName}</CTableDataCell>
<CTableDataCell>
<div>{row.lastName}</div>
</CTableDataCell>
<CTableDataCell className="column-overflow">
{row.email}
</CTableDataCell>
<CTableDataCell>{row.role}</CTableDataCell>
<CTableDataCell>{row.createdAt}</CTableDataCell>
<CTableDataCell>{row.updatedAt}</CTableDataCell>
<CTableDataCell>
<strong>{row.lastLogin}</strong>
</CTableDataCell>
<CTableDataCell>
<CDropdown>
<CDropdownToggle color="transparent">
Dropdown button
</CDropdownToggle>
<CDropdownMenu>
<CDropdownItem className="dropdown-item pointer">
View
</CDropdownItem>
<CDropdownItem className="dropdown-item pointer">
Edit
</CDropdownItem>
<CDropdownItem
className="dropdown-item text-danger pointer"
onClick={() => {
setSelectedUser(row);
setVisible(true);
}}
>
Delete
</CDropdownItem>
</CDropdownMenu>
</CDropdown>
</CTableDataCell>
</CTableRow>
);
})}
</CTable>
<Modal
visible={visible}
user={selectedUser}
onClose={() => setVisible(false)}
/>
</>
);
};
Modal.js
const Modal = ({ visible, onClose, user }) => {
return (
<>
<CModal visible={visible} onClose={onClose}>
<CModalHeader onClose={onClose}>
<CModalTitle>Delete {user.firstName}?</CModalTitle>
</CModalHeader>
<CModalBody>
Are you sure you want to delete {user.firstName}? (He's a good guy.)
</CModalBody>
<CModalFooter>
<CButton color="secondary" onClick={onClose}>
Yeah he is a good guy
</CButton>
<CButton color="primary">Nuke 'Em!</CButton>
</CModalFooter>
</CModal>
</>
);
};
Note: As an added bonus, I added an example of passing the selected user to the Modal so only one instance of the Modal component exists in the page.
Working CodeSandbox: https://codesandbox.io/s/cold-leftpad-l5ivqy?file=/src/Modal.js
Users.js Rendered
Modal.js Rendered
I want to create a model in react material UI library which shows the info on the landing page as the user logs in.
I have tried triggering with timeout but im unable to close the modal once it appears.
const[post,setPost] = useState('')
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(false);
};
useEffect(() =>{
setTimeout(()=>{
setNotesPopup(true);
},3000);
},[]);
const Popup=((props) =>{
return(props.trigger)
})
return (
<>
<NavigationBar/>
<SydBackdrop openBackdrop={openBackdrop} />
<Container maxWidth="xl" className="mb-2">
<Card className={classes.card}>
<CardHeader title="Customer Search Parameters" className={classes.cardHeader}/>
<CardContent>
<Dialog trigger={notesPopup} setTrigger={setNotesPopup} aria-labelledby="customized-dialog-title" open={open}>
<DialogTitle id="customized-dialog-title"> UI changes</DialogTitle>
<DialogContent dividers><Typography gutterBottom><Markdown>{post}</Markdown></Typography></DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">OK</Button>
</DialogActions>
</Dialog>
</>
)
}
Consider using a global state that is available to your entire app like Redux or React's context api.
Put a property that has modalStatus as false. When the user logs in, update that value in your global state to true and when the user closes the modal, it sets it to false
I've been trying to pass in a boolean useState between two pages, using the best practice shown here: How to call useState from another Page?
My main objective is to show a success alert on a different page, once a form is successfully submitted on "./demo"
Demo.js holds a dialog with a submit button that sets the setSuccess to true.
import Alert from "./alert";
export default function AlertDialog() {
const [success, setSuccess] = React.useState(false); // <- Hides and Shows the Alert Message
const handleSubmit = () => {
return (
<Alert
setSuccess={() => {
setSuccess(true); // <- How I am trying to setSuccess to true.
}}
/>
);
};
return (
<DialogActions>
<Button
onClick={handleSubmit}
color="primary"
autoFocus
component={RouterLink}
to={"/"}
>
Submit
</Button>
</DialogActions>
);
Alert.js has an alert message that appears once success is set to true.
export default function Alerts(props) {
// const [open, setOpen] = React.useState(false);
const { success, setSuccess } = props;
return (
<div>
<Collapse in={success}>
<Alert
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={() => {
setSuccess(false);
}}
>
<CloseIcon fontSize="inherit" />
</IconButton>
}
>
Form Successfully Submitted
</Alert>
</Collapse>
<Button
disabled={success}
variant="outlined"
component={RouterLink}
to={"/demo"}
>
Go Back to Submit Form
</Button>
</div>
)
;
}
Could someone explain how I can have the success alert appear after submission? If you wish to take a deeper dive, please visit here https://codesandbox.io/s/alert-test-qhkbg?file=/alert.js
I think what you're looking for here is passing state through React Router. Right now your alert isn't updating because your URL is changing when the submit button is pressed.
Check out this sandbox. I'm passing a message and a property to get the alert to render within the / route with different logic.
This is the key snippet:
<Button
color="primary"
autoFocus
component={RouterLink}
to={{
pathname: "/",
state: { message: "hello, i am a state message", open: true }
}}
>
Submit
</Button>
And then in your alert component at the url / you can:
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
props.location.state?.open ? setOpen(true) : setOpen(false);
}, [props]);
// This message is what is being passed. Could be anything.
console.log(props.location.state);
I have a React app which uses Material UI for it's interface. I've created a custom button component which styles the default Material UI button and also uses redux.
The render() function of my button component looks like this:
return (
<div className={classes.buttonWrapper}>
<Button
ref={this.props.innerRef}
disabled={loading || disabled}
onClick={this.handleClick}
{...other}>
<React.Fragment>
{children}
{this.buildLoader(loading, classes)}
</React.Fragment>
</Button>
</div>
);
What I want is to be able to include this button on a page and have the UI trigger its click event by other means other than clicking on it. For example, on a login form I want a user who currently has focus on the password textbox to be able to trigger the button click by hitting the Return/Enter key.
I'm sure I need to use the concept of forwarding refs in React, but I'm fairly new to React and can't get it working. You can see on my button I've defined a ref set to this.props.innerRef. My button component (called WaitingButton) is exported like this:
const withInnerRef = React.forwardRef((props, ref) => <WaitingButton
innerRef={ref} {...props}
/>);
var component = withStyles(styles)(withInnerRef);
export default connect(mapStateToProps, mapDispatchToProps)(component);
I've then added this button to a form like this:
<Paper>
<TextField
style={{marginBottom: '8px'}}
label="A textbox!"
fullWidth
onKeyPress={(e) => { if (e.key === "Enter") this.triggerClick(); }} />
<WaitingButton
ref={this.submitButton}
variant="contained"
color="primary"
onClick={(e) => {
console.log('Button clicked :)', e.target);
}}>
Press enter in textbox!
</WaitingButton>
</Paper>
See I've assigned the button's ref and in this page's constructor I've initialised the ref in the constructor using this.submitButton = React.createRef();
Finally the triggerClick looks like this:
triggerClick() {
console.log('CLICK', this.submitButton.current);
this.submitButton.current.click();
}
When I hit enter in the textbox, I can inspect the value assigned to this.submitButton.current and can see it is the Redux connect object that I've wrapped my button with. However, I also get the error this.submitButton.current.click is not a function so clearly the ref isn't getting forwarded all the way to the button itself.
I'm afraid I'm a bit lost so appealing for your help!
Just want to ensure, what you want is: when user press Enter while typing on the textfield, the button will show a loading visual, right?
I think you don't have to pass ref to the button component, you could just pass state isLoadingShown into your WaitingButton
WaitingButton.js
return (
<div className={classes.buttonWrapper}>
<Button
ref={this.props.innerRef}
disabled={loading || disabled}
onClick={this.handleClick}
{...other}>
<React.Fragment>
{children}
{this.props.isLoadingShown && this.buildLoader(loading, classes)}
</React.Fragment>
</Button>
</div>
);
Then in the form component
state = {
isLoadingShown: false,
}
triggerClick() {
this.setState({ isLoadingShown: true })
}
render(){
...
<Paper>
<TextField
style={{marginBottom: '8px'}}
label="A textbox!"
fullWidth
onKeyPress={(e) => { if (e.key === "Enter") this.triggerClick(); }} />
<WaitingButton
variant="contained"
color="primary"
isLoadingShown={this.state.isLoadingShown}
onClick={(e) => {
console.log('Button clicked :)', e.target);
}}>
Press enter in textbox!
</WaitingButton>
</Paper>
...
}
don't forget to set isLoadingShown to false again in componentWillUnmount
I just tried to reproduce your case. And I created a codesandbox for it. I think I found the problem. It seems React.forwardRef only works with prop name forwardedRef so try to rename the innerRef property to forwardedRef in your code.
const withInnerRef = React.forwardRef((props, ref) => <WaitingButton
forwardedRef={ref} {...props}
/>);
and also in your render() function
<Button
ref={this.props.forwardedRef}
disabled={loading || disabled}
onClick={this.handleClick}
...
You can try it with my simplified codesandbox https://codesandbox.io/s/intelligent-cori-rb5ce
I have a simple form presented with a modal that is intended to update my redux store with the value typed into the textbox.
I'm trying to use the useState hook to set the value of the textbox on change. When printing to the console I can see that the value of my variable - "note", is the correct value. However when trying to submit and passing "note" into my updateCheckpoint function, the value is undefined.
Additionally upon opening the dialog and submitting a second time, the note has the correct value.
function CompetencyCheckpoint(props)
{
const dispatch = useDispatch();
let [note, setNote] = useState();
function updateCheckpoint(note){
if(note != null){
console.log("Here is our note : " + note);
}else{
console.log("oh no, we didn't get the note");
}
}
console.log(note);
return (
<div className="checkpoint-container">
<i className="material-icons" onClick={()=>
dispatch(openDialog({
children: (
<React.Fragment>
<DialogTitle id="form-dialog-title">Add Note</DialogTitle>
<DialogContent>
<DialogContentText>
Enter a note.
</DialogContentText>
<TextField
id="filled-textarea"
label="New Note"
placeholder="Enter note here"
multiline
value={note}
onChange={(e) => setNote(e.target.value)}
margin="normal"
variant="filled"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={()=> dispatch(closeDialog())} color="primary">
Cancel
</Button>
<Button onClick={() => updateCheckpoint(note)} color="primary">
Add
</Button>
</DialogActions>
</React.Fragment>
)
}))}>
note_add
</i>
</div>);
}
export default (CompetencyCheckpoint);
The issue is that your JSX for the dialog content is captured at the time of opening the dialog and will not update as re-renders occur due to changing the note state. This is why #AhmadNoor's solution causes further issues -- it changes your TextField from uncontrolled to controlled, but it never receives the updated note value.
Your openDialog function should just control the open property on the Dialog. CompetencyCheckpoint should change to include the JSX of the entire Dialog directly rather than as an argument to the openDialog dispatch, so that it is included in re-renders due to changes to the note state.
Here's one way it could work (I'm assuming in my example that these are Material-UI components):
import React from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
TextField
} from "#material-ui/core";
function CompetencyCheckpoint(props) {
const [open, setOpen] = React.useState(false);
const [note, setNote] = React.useState("");
function updateCheckpoint(note) {
if (note != null) {
console.log("Here is our note : " + note);
} else {
console.log("oh no, we didn't get the note");
}
}
console.log(note);
return (
<div className="checkpoint-container">
<i className="material-icons" onClick={() => setOpen(true)}>
note_add
</i>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle id="form-dialog-title">Add Note</DialogTitle>
<DialogContent>
<DialogContentText>Enter a note.</DialogContentText>
<TextField
id="filled-textarea"
label="New Note"
placeholder="Enter note here"
multiline
value={note}
onChange={e => setNote(e.target.value)}
margin="normal"
variant="filled"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)} color="primary">
Cancel
</Button>
<Button onClick={() => updateCheckpoint(note)} color="primary">
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default CompetencyCheckpoint;
I was able to get the code working as expected. I'm not sure, maybe it had something to do with the way I was using the children: keyword, I'll check the docs to see what exactly that was doing. I was following a precedent I didn't fully understand.
Here is the solution I came up with. Basically I just added a state variable to control weather or not the dialog should show. I guess this also simplifies it so that i'm not calling redux to handle the Dialog toggle.
https://codesandbox.io/embed/focused-allen-cg1lq?fontsize=14
There may be further room for refactoring by making the Dialog it's own Component.
Thank you everyone who helped out, especially T.J and Ryan!