React form onChange is a character behind [duplicate] - javascript

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

Related

Hidden password only works in one input box

I have this JS code that shows 2 input boxes that asks for a password: 1) Password 2) Confirm Password. However, the clickShowPassword() is only connected to Password.
[Output] [1]: https://i.stack.imgur.com/IZoa3.png
Here's my whole js file that is connected to an react application.
import React from "react";
function ShowHidePassword(){
const [values, setValues] = React.useState({
password: "",
passwordConf: "",
showPassword: true,
});
const clickShowPassword = (event) => {
setValues({ ...values, showPassword: !values.showPassword });
event.preventDefault();
};
const passwordChange = (prop) => (event) => { setValues({ ...values, [prop]: event.target.value }); };
const mouseDownPassword = (event) => { event.preventDefault(); };
return (
<div>
<input
type={values.showPassword ? "text" : "password"}
onChange={passwordChange("password")}
value={values.password} id="signup-password"
placeholder="PASSWORD"
/>
<input
type={values.showPassword ? "text" : "passwordConf"}
onChange={passwordChange("passwordConf")}
value={values.passwordConf} id="signup-password-confirm"
placeholder="CONFIRM PASSWORD"
/>
<br/>
<button className="hide-password2" onClick={clickShowPassword} onMouseDown={mouseDownPassword}>
{values.showPassword===false? <i className="bi bi-eye-slash"></i> :<i className="bi bi-eye"></i> } Show Password
</button>
</div>
);
};
export default ShowHidePassword;
In your second input you used passwordConf as an input type, I think this happened because u copied the first input and batch-replaced all "password" words with "passwordConf", happens to the best of us :)

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

State not being set by setState method

I am using React JS.
Here is my React class:
class SomeClass extends React.Component{
constructor(props){
super(props);
this.state = {
passAccount: {
email: "Email"
},
errorMessage: ''
};
}
submitRequest = (event) =>{
//this.state.passAccount.email === 'Email' ? this.setState({errorMessage:'Please enter a valid email'}) : axios.post(`http://localhost:5000/otp/generate-passcode/${this.state.passAccount.email.toString()}`, this.state.passAccount)
axios.post(`http://localhost:5000/generate/${String(this.state.passAccount.email)}`)
.then((response) => {
let result = response.data;
}).catch((error) =>{
this.setState({errorMessage: ''});
});
console.log(`submitRequest email: `, this.state.passAccount.email);
}
handleChange = (event) =>{
console.log(`input detected`);
let request = this.state.passAccount;
let requestValue = event.target.value;
this.setState({passAccount: requestValue});
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<Form.Label>Email Address</Form.Label>
<Form.Control type="text" value={this.state.email} onChange={this.handleChange} placeholder="Enter Email Address" style={{width: '25rem'}}/>
</Form.Group>
<Button type="submit" onClick={() => this.submitRequest()}>Get OTP</Button>
<Button type="submit">Sign In</Button>
</Form>
);
}
}
export default SomeClass;
In Chrome console, this is what I am getting:
input detected
submitRequest email: Email //here is what I want to fix
Form Submitted Successfully
My question is:
In the line where it says in the console:
submitRequest email: Email //here is what I want to fix, for some reason the setState method is not working what is the reason behind that ?
Is the error in the handleChange method or in the submitRequest method ? what is a possible fix ?
Thanks.
When you this.setState({passAccount: requestValue}); you are setting passAccount to current value edited in form. But passAccount is an object with email property.
So I would suggest to modify your code in this way:
handleChange = (event) =>{
console.log(`input detected`);
let request = Object.assign({}, this.state.passAccount); // copy this.state.passAccount into request
request.email = event.target.value; // change request email
this.setState({ passAccount: request }); // set passAccount
}
You have declared your state variable passAccount as an object which contains an email property. If you want to update this email property then in your handleChange function, you need to update the state like this:
this.setState({ passAccount: { email: requestValue });

React Input Form Value In An Array

I am trying to build a registration form, I have built the login form and it works perfectly, but the registration form is quite off.
First I used the react-dev tools to inspect what is going on and I realized that each input value coming from the registration form happens to be in an array.
I went back to the login form to inspect and saw that the value of the input fields is in the right format (strings). But the value from each specific input field in the registration form is in an array.
What could I be doing wrong?
I tried to replicate what I did in the login form in the registration form, but it is still coming in an array. Also, should hooks be kept in its own separate file?
This is what I see from the react-dev tools for the registration form.
Here is a code snippet of what I did.
const Signup = (props) => {
const authContext = useContext(AuthContext);
const { register, clearErrors, isAuthenticated, error } = authContext;
const [loadBtn, updateLoadBtn] = useState(false);
const [user, setUser] = useState({
firstName: "",
lastName: "",
email: "",
password: "",
username: "",
phoneNumber: "",
});
useEffect(() => {
if (isAuthenticated) {
successMessage();
props.history.push("/dashboard");
}
if (error) {
missingValue(error);
updateLoadBtn(false);
clearErrors();
}
//eslint-disable-next-line
}, [isAuthenticated, error, props.history]);
const { firstName, lastName, email, password, username, phoneNumber } = user;
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
const onSubmit = (e) => {
e.preventDefault();
updateLoadBtn(true);
if (
!firstName ||
!lastName ||
!email ||
!password ||
!username ||
!phoneNumber
) {
missingValue("Please enter all fields");
updateLoadBtn(false);
clearErrors();
} else {
register({
firstName,
lastName,
email,
password,
username,
phoneNumber,
});
}
};
return (
<Fragment>
<ToastContainer />
<RegContainer className="container-fluid py-4">
<RegInfo />
<RegColumn
onChange={onChange}
onSubmit={onSubmit}
firstName={firstName}
lastName={lastName}
email={email}
password={password}
phoneNumber={phoneNumber}
loadBtn={loadBtn}
/>
</RegContainer>
</Fragment>
);
}
That is the file responsible for handling the registration.
Here is the custom component
const RegColumn = ({
firstName,
onSubmit,
onChange,
lastName,
username,
password,
phoneNumber,
email,
loadBtn,
}) => {
const bodyStyle = document.querySelector("body").style;
bodyStyle.backgroundImage = "linear-gradient(to bottom, #F6F6F2, #C2EDCE)";
bodyStyle.backgroundRepeat = "no-repeat";
bodyStyle.overflow = "hidden";
bodyStyle.height = "100%";
bodyStyle.fontFamily = "Rubik, sans-serif";
return (
<Fragment>
<div id="reg-column">
<h3 style={{ color: "#388087" }}>REGISTRATION</h3>
<Form divid="form-row1-label" onSubmit={onSubmit}>
<LabelContainer id="form-row1-label">
<LabelContainer id="firstNameLabel">
<LabelInfo labelfor="firstName" labeltitle="First Name" />
</LabelContainer>
<LabelContainer id="lastNameLabel">
<LabelInfo labelfor="lastName" labeltitle="Last Name" />
</LabelContainer>
</LabelContainer>
<InputContainer id="form-row1-input">
<InputContainer id="firstNameInput">
<Input
type="text"
name="firstName"
value={firstName}
id="firstName"
onChange={onChange}
/>
</InputContainer>
<InputContainer id="lastNameInput">
<Input
type="text"
onChange={onChange}
name="lastName"
value={lastName}
id="lastName"
/>
</InputContainer>
// ...
Thank you.
Within your Signup form, you have this...
// ...
const onChange = (e) =>
setUser({ ...user, [e.target.name]: [e.target.value] });
What's happening above?
The onChange() is responsible for updating the values to be submitted.
Within setUser, you are passing a value as a literal array using [e.target.value]
Solution:
Remove the literal array [] and pass value as it's received i.e. e.target.value
The left hand side, e.target.name, is fine since you are actually using a computed property name.
You can also read more about handling forms in react.

useState doesn't update its value

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

Categories