Why is my useState hook not holding the value of my variable? - javascript

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!

Related

How to create a Modal in react triggered without button

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

Passing a single boolean useState between two pages using Material UI web components

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

REACT - Impossible to pop my DIALOG component onClick

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

Material-ui picker: how to hide text field and open modal with button

I'm using version 3.2.6 of material-ui pickers to create a component that renders differently on mobile and desktop.
On desktop I'm displaying a regular inline datepicker with textinput, and for mobile I only want to display a dateicon button which opens up the modal
From what I can see the material-picker api does not have a prop to hide the textfield, and the DatePickerModal is not a standalone component.
I saw solutions using ref to open the with a button, but that seemed to be for older versions of the library, and I couldn't get it working.
Any tips on how this can be achieved with the latest version? Is there some props I can pass down to the TextField component to hide it?
You can hide textfield by passing custom component. It will be like
function ControllingProgrammaticallyExample() {
const [isOpen, setIsOpen] = useState(false);
const [selectedDate, handleDateChange] = useState("2018-01-01T00:00:00.000Z");
return (
<div className={classes.container}>
<Button onClick={() => setIsOpen(true)}> Open picker </Button>
<DatePicker
open={isOpen}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
value={selectedDate}
onChange={handleDateChange}
TextFieldComponent={() => null}
/>
</div>
);
Official example: https://material-ui-pickers.dev/guides/controlling-programmatically
Just use the renderInput method
renderInput={
hideInput ?
({ inputRef, inputProps, InputProps }) => (
<Box ref={inputRef}>
{InputProps?.endAdornment}
</Box>
) :
(params => <TextField {...params} />)
}
where hideInput is boolean you can use to toggle betwen the views.

In React how can I trigger a custom button's click event from a different compoennt's event handler?

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

Categories

Resources