Reputation: 63
I have recently started learning react and I have a component that has a login form:
import Joi from "joi";
import { useState } from "react";
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const loginSchema = Joi.object().keys({
username: Joi.string().required(),
password: Joi.string().required(),
});
const [errors, setErrors] = useState({ username: null, password: null });
const handleSubmit = (form) => {
form.preventDefault();
validate();
};
const validate = () => {
const { error } = loginSchema.validate(
{ username, password },
{ abortEarly: false }
);
if (!error) return null;
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
setErrors({ ...errors, [key]: value });
}
return errors;
};
return (
<div className="container">
<div>
<h1>Login</h1>
</div>
<div className="card">
<div className="card-body">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="username">Username</label>
<input
name="username"
type="text"
className="form-control"
id="username"
placeholder="Enter username"
value={username}
onChange={(username) => setUsername(username.target.value)}
/>
{errors.username && (
<div className="alert alert-danger">{errors.username}</div>
)}
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
name="password"
type="password"
className="form-control"
id="password"
placeholder="Password"
value={password}
onChange={(password) => setPassword(password.target.value)}
/>
{errors.password && (
<div className="alert alert-danger">{errors.password}</div>
)}
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
{/* Delete later */}
<h4>{JSON.stringify(errors)}</h4>
</div>
</div>
</div>
);
};
export default Login;
Here, I am trying to set the values of username and password in the errors object, but for some reason, only the password property gets set, the username property remains null. I have used object destructuring but it still doesn't set the value, please help.
Upvotes: 0
Views: 513
Reputation: 281734
State updates are not reflected immediately in React, and since you loop over your data to set the errors object multiple times, it only sets in the last key due to batching in event handlers and the fact that state updates are affected by closures.
You should instead create an updated objected and set it in state once
let newErrors = {};
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
newErrors = { ...newErrors, [key]: value };
}
setErrors(prev => ({...prev, ...newErrors}));
Upvotes: 1
Reputation: 18113
I think the problem comes from this code
for (const item of error.details) {
let key = item.path[0];
let value = item.message;
setErrors({ ...errors, [key]: value });
}
Here you are calling setErrors
in a for loop with the same errors
object so only the last field is taken into account.
Here is a possible solution :
if (!error) return null;
const newErrors = error.details.reduce((acc, item) => {
const key = item.path[0];
const value = item.message;
acc[key] = value;
return acc;
}, {...errors});
setErrors(newErrors);
Upvotes: 1