useState doesn't update its value - javascript

I'm creating a input form for an e-mail and i have a delayed onChange on it to not call the api too many times.
Here's my code:
const InformationCollection = (props) => {
const [email, setEmail] = useState()
const [collectedEmail, setCollectedEmail] = useState(1)
useEffect(() => {
let timeout = setTimeout(() => {
setCollectedEmail(email)
console.log(collectedEmail)
}, 500)
return () => {
clearTimeout(timeout)
}
}, [email])
return (
<div className="form-group">
<label htmlFor="inputmail">Email address</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)
}
export default InformationCollection
On this line if i type "1" console.log(e.target.value + "this is what is set" + email), e.target.value is 1, but email is undefined.
On the next character "12", e.target.value is 12 but email is 1
Can anyone help with this?
UPDATE:
The solution is to have 2 useEffectHooks. One for the value in the form email and one for the delayed value collectedEmail
Second solution is to do fetch inside the first useEffect hook
const InformationCollection = (props) => {
const [email, setEmail] = useState()
const [collectedEmail, setCollectedEmail] = useState()
useEffect(() => {
let timeout = setTimeout(() => {
//fetch directly here
setCollectedEmail(email)
console.log(collectedEmail)
}, 500)
return () => {
clearTimeout(timeout)
}
}, [email])
useEffect(() => {
//fetch() here
console.log(collectedEmail) //right value
}, [collectedEmail])
return (
<div className="form-group">
<label htmlFor="inputmail">Email address</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)
}
export default InformationCollection

state is updated asynchronously, that's why email is undefined for the first time when you try to log it after updating the state.
You can log the email inside useEffect hook which will be called after email has changed.
On the next character "12", e.target.value is 12 but email is 1
email is 1 because when onChange event fired for the first time, email was undefined but when onChange event fires for the second time, email had already been updated asynchronously to 1

Isn't this expected behaviour? email is always the value before the change inside the onChange handler. Because the re-render hasn't happened yet.
To see the value rendered do this:
return (
<div className="form-group">
<label htmlFor="inputmail">Email address: { email }</label>
<input
type="email"
className="form-control"
onChange={(e) => {
setEmail(e.target.value)
console.log(e.target.value + "this is what is set" + email)
}}
aria-label="Enter e-mail address"
/>
</div>
)

Related

How to clean input fields after another operations in a function in React?

I have a button and 2 input field and I am sending these input field values to backend. etc doing some operations. After doing operations in addCustomer function, I want to reset input fields but it is not working.
Here is the code:
function TableFooterPanel(props) {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const addNewCustomer = async (name, surname) => {
await service.addCustomer(name, surname);
props.funcParam();
setFirstName('');
setLastName('');
}
var isButtonDisabled = false;
(firstName.length <= 3 || lastName.length <= 3) ? isButtonDisabled = true : isButtonDisabled = false;
return (
<>
<Card className='buttonFooter'>
<Form className='buttonFooter'>
<input type="text" placeholder="First Name" defaultValue={firstName} onChange={e => setFirstName(e.target.value)}></input>
<input type="text" placeholder="Last Name" defaultValue={lastName} onChange={e => setLastName(e.target.value)}></input>
<Button disabled={isButtonDisabled} onClick={() => addNewCustomer(firstName, lastName)} className='addButton'>Add</Button>
<label hidden={!isButtonDisabled} className='labelStyle'>Field lengths must be at least 4 character</label>
</Form>
</Card>
</>
);
}
export default TableFooterPanel;
Here everything is working good except
setFirstName('');
setLastName('');
they are not resetting or setting to another value. What is the reason for that and how can I achieve it ?
The problem is that you're setting the defaultValue prop and not the value prop.
<input type="text" placeholder="First Name" value={firstName} onChange={e => setFirstName(e.target.value)} />
<input type="text" placeholder="Last Name" value={lastName} onChange={e => setLastName(e.target.value)} />
From docs:
https://reactjs.org/docs/forms.html#controlled-components
Maybe you could use a state hook to track the server response.
If valid response then send back true and trigger reset text-fields function
Without seeing your service code, I am not sure I can 100% guarantee any of this advice. But it seems to me that you should be doing that work in a .then() or a .finally()
for example:
const addNewCustomer = async (name, surname) => {
await service.addCustomer(name, surname)
.then(() => {
props.funcParam();
setFirstName('');
setLastName('');
})
};
You could also do this in a .finally if you want to to execute regardless of the results from the server.

An Elegant Way to add Confrim Password field in React

I have a project in which I have to add a registration form and I want to to validate that the password and confirm fields are equal without clicking the register button.
If password and confirm password field will not match, then I also want to put an error message at side of confirm password field and disable registration button.
I had these for handle password and username
const LoginForm = ({ register = false }) => {
const [isLoading, setLoading] = React.useState(false)
const [errors, setErrors] = React.useState([])
const [username, setUsername] = React.useState('')
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const handleUsernameChange = React.useCallback(
(e) => setUsername(e.target.value),
[setUsername]
)
const handleEmailChange = React.useCallback(
(e) => setEmail(e.target.value),
[]
)
const handlePasswordChange = React.useCallback(
(e) => setPassword(e.target.value),
[]
)
and Got handle submit
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
try {
let data, status
if (register) {
;({ data, status } = await UserAPI.register(username, email, password))
} else {
;({ data, status } = await UserAPI.login(email, password))
}
if (status !== 200 && data?.errors) {
setErrors(data.errors)
}
if (data?.user) {
// We fetch from /profiles/:username again because the return from /users/login above
// does not contain the image placeholder.
const { data: profileData, status: profileStatus } = await UserAPI.get(
data.user.username
)
if (profileStatus !== 200) {
setErrors(profileData.errors)
}
data.user.effectiveImage = profileData.profile.image
window.localStorage.setItem('user', JSON.stringify(data.user))
setCookie('auth', data.user.token)
mutate('user', data.user)
Router.push('/')
}
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
I want to add new confirm password field to this
<form onSubmit={handleSubmit}>
<fieldset>
{register && (
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Username"
value={username}
onChange={handleUsernameChange}
/>
</fieldset>
)}
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="email"
placeholder="Email"
value={email}
onChange={handleEmailChange}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</fieldset>
<button
className="btn btn-lg btn-primary pull-xs-right"
type="submit"
disabled={isLoading}
>
{`${register ? 'Sign up' : 'Sign in'}`}
</button>
</fieldset>
</form>
What is the most elegant way to add confirm password validation?
The elegant way to create a form, in general, is using a form library. The form libraries will make your work easier, more elegant, and more developable. Most of them have a technique to use a function or a scheme as a validator that will help you certify your password.
The most popular form libraries currently are Formik and React Hook Form and if you are using Redux you can use Redux Form.
In case you want to continue your current way of handling the form the best possible name for the second field is passowrdConfirmation is the best name in my Idea. Furthermore, you can create a validation function that you process before every field change(using a useEffect hook) or before submitting(using onSubmit event on form element).
You can use "useEffect" hook to listen to password and confirm password inputs.
Here is a simple example: https://codesandbox.io/s/solitary-brook-tw15c

React form onChange is a character behind [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I'm trying to make a form with React but having some issues with the password and confirm password field.
What I want is basically to have a user type a password and once they confirm, it should display in real time whether the password matches the confirm password field, or if the passwords do not match.
const handlePasswordChange = (event) => {
const { name, value } = event.target;
setCustomerData(prev => ({
...prev,
[name]: value
}));
// setPasswordError displays the error on the form to the user
if(customer.password === customer.confirmPassword) {
setPasswordError('passwords match')
} else if(customer.password !== customer.confirmPassword) {
setPasswordError('Passwords dont match')
} else {
setPasswordError('');
}
console.log("password: " + customer.password);
console.log("Confirm password: " + customer.confirmPassword);
}
State Object
const [customer, setCustomerData] = useState({
firstName: "",
lastName: "",
emailAddress: "",
mobileNumber: "",
dateOfBirth: new Date(),
password: "",
confirmPassword: ""
});
// Displays error using spring boot on the backend
----------
const [firstNameError, setFirstNameError] = useState("");
const [lastNameError, setLastNameError] = useState("");
const [emailAddressError, setEmailAddressError] = useState("");
const [mobileNumberError, setMobileNumberError] = useState("");
const [dateOfBirthError, setDateOfBirthError] = useState("");
const [passwordError, setPasswordError] = useState("");
Form password and confirm password fields
{/*Password*/}
<div className="form-group">
<label htmlFor="password" className="register-form-labels">Password</label>
{passwordError ? <span className="field-validation-styling">{passwordError}</span> : ''}
<input type={passwordShown ? "text" : "password"}
onFocus={(e) => setPasswordError('')}
className="shadow-none form-control register-form-input-field"
name="password"
placeholder="Enter Password"
value={customer.password}
onChange={handlePasswordChange}
/>
</div>
{/*Confirm Password*/}
<div className="form-group">
<label htmlFor="confirmPassword" className="register-form-labels">Confirm Password</label>
<input type={passwordShown ? "text" : "password"}
minLength="8"
className="shadow-none form-control register-form-input-field"
name="confirmPassword"
placeholder="Confirm Password"
value={customer.confirmPassword}
onChange={handlePasswordChange} />
</div>
But when i type in my password, the input is a character behind. This is what i mean
So useState doesn't update immediately, you need something to listen to the changes and update afterwards.
As you are calling handlePasswordChange on change, and then in the same function checking for equality, the customer state is still the "old" state. It wont become the "new" state until reload.
The use of useEffect would be fine here, listening for changes in the customer object, and then acting upon them;
// Function to set passwords
const handlePasswordChange = (event) => {
const { name, value } = event.target;
setCustomerData((prev) => ({
...prev,
[name]: value
}));
// setPasswordError displays the error on the form to the user
console.log("password: " + customer.password);
console.log("Confirm password: " + customer.confirmPassword);
};
//useEffect with a dependency of customer
useEffect(
(_) => {
checkPassword(customer);
},
[customer]
);
// separate check password function
const checkPassword = (customer) => {
if (customer.password === customer.confirmPassword) {
setPasswordError("passwords match");
} else if (customer.password !== customer.confirmPassword) {
setPasswordError("Passwords dont match");
} else {
setPasswordError("");
}
};

I want to add user input object of an array with react useState(), when I send it it does not update the questions?

I have an object that has quizzes which is an array of objects. I would like the user to enter questions, correct_answer and incorrect_answers and send it to my database but its not doing it, it is sending it empty. I am having trouble with the onInputChange function to make my inputs go through
const [questions, setQuestions] = useState({
quizes:[{
question : '',
correct_answer: '',
incorrect_answers:[]
}]
});
useEffect(() => {
loadQuestions(id);
}, []);
const loadQuestions = async ( id ) => {
const result = await api.get(`/assignments/${id}`);
setQuestions(result.data);
};
// const {quizes,question, correct_answer} = questions
const onInputChange = e => {
const value = e.target.value
setQuestions({...questions, [e.target.name]: value });
};
// const addQuestion = async (id) => {
// await api.post(`/assignments/addquestion/${id}`);
// loadQuestions();
// };
const onSubmit = async e => {
e.preventDefault();
await api.post(`/assignments/addquestion/${id}`, questions);
history.push("/dashboard");
};
return (
<div className="container">
{/* {console.log('this is the name of the question ** '+ questions.quizes.map(x=>x.question))} */}
<div className="py-4">
<form onSubmit={e => onSubmit(e)}>
{/* {questions.quizes.map(x=>{ */}
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Enter question"
name= {questions.quizes.question}
value = {questions.quizes.question}
onChange={e => onInputChange(e)}
/>
</div>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Enter question"
name='correct_answer'
value={
questions.quizes.correct_answer
}
onChange={e => onInputChange(e)}
/>
</div>
This is the output, it leaves the quizzes part empty
{
"createdAt": "2021-01-13T02:54:39.710Z",
"_id": "5fff630624f06bfd45f6bcd2",
"title": "testing this hhs",
"quizes": [
{
"incorrect_answers": [],
"_id": "5fff630d24f06bfd45f6bcd3",
"question": "",
"correct_answer": ""
}
],
"__v": 0
}
You can find here an example of something similar to what you're trying to achieve.
https://codesandbox.io/s/array-form-example-fg2yr?file=/src/App.js
Don't forget to split your code in multiple components. You can also use react-fiinal-form to better manage your forms, with react-final-form-arrays in your case

HandleChange/setState stopped working after migrating to react useState hook

I had a component using classBased react, and it was working fine, I decided to switch to usestate and it stopepd working. Now the handlechange records the state in a random manner, but it doesnt work properly
My handlechange and the useState
const [state, setState] = useState({
email: "",
password: "",
wrongCombo: false,
serverError: false
})
const handleChange = (evt) => {
const target = evt.target;
const value = target.value;
const name = target.name;
console.log(name)
console.log(value)
setState({
[name]: value
});
}
My handlesubmit (it detects random values in that console.log, icant find the logic, but the log wont get both values as per inputed in the handlechange)
const handleSubmit = (event) => {
event.preventDefault();
const { email, password } = state;
console.log(state)
props.login(email, password, "login").
then(data => {
}).catch((err) => {
if (err == "Error: Request failed with status code 403") {
setState({
wrongCombo: true
}, () => {
})
} else if (err == "Network error") {
setState({
serverError: true
})
}
})
}
And this is my render
<div>
<form>
{state.wrongCombo ? <Alert variant="danger" dismissible onClose={handleDismiss}> Wrong email and password combination </Alert> : null}
{state.serverError ? <Alert variant="danger" dismissible onClose={handleDismiss}> Server Error </Alert> : null}
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" onChange={handleChange} />
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" name="password" class="form-control" id="exampleInputPassword1" placeholder="Password" onChange={handleChange} />
</div>
<div className="text-center buttonContainer">
<button type="submit" class="btn btn-primary buttonLogin" onClick={handleSubmit}>Submit</button>
</div>
</form>
</div>
From the docs on useState:
unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
You must replace all values in a useState object when updating them. You are only providing an object to the updater with one of the keys, so only that key will be retained.
A simple pattern for doing this would be to spread the previous state before passing your update:
setState(prev => ({...prev, [newKey]: newVal }));

Categories