Reputation: 1287
I have a ReactJS app. I am trying to implement form validation by the accepted answer here as my example. I had to adapt it some because I am using a Function and not a Component (i.e. I don't have a constructor).
Here is the whole function:
import React, { useState } from "react";
import Config from 'config';
import PropTypes from "prop-types";
import "./Login.css";
export default function ChangePassword({ setToken }) {
const [fields, setFields] = useState({});
const [errors, setErrors] = useState({});
const tokenString = sessionStorage.getItem("token");
const token = JSON.parse(tokenString);
let headers = new Headers({
"Accept": "application/json",
"Content-Type": "application/json",
'Authorization': 'Bearer ' + token.token
});
async function changePassword(password) {
return fetch(Config.apiUrl + "/api/Users/ChangePassword", {
method: "POST",
headers: headers,
body: password
})
.then((response) => {
return response.json();
});
}
function handleValidation() {
let formErrors = {};
let formIsValid = true;
//Password
if (!fields["password"]) {
formIsValid = false;
formErrors["password"] = "Cannot be empty";
}
if (typeof fields["password"] !== "undefined") {
if (!fields["password"].match(/^[a-zA-Z0-9!@#$%^&?]+$/)) {
formIsValid = false;
formErrors["password"] = "Only accepted";
}
}
//Confirm Password
if (!fields["confirmPassword"]) {
formIsValid = false;
formErrors["confirmPassword"] = "Cannot be empty";
}
if (typeof fields["confirmPassword"] !== "undefined") {
if (!fields["confirmPassword"].match(/^[a-zA-Z0-9!@#$%^&?]+$/)) {
formIsValid = false;
formErrors["confirmPassword"] = "Only accepted";
}
}
if (fields["confirmPassword"] != fields["password"]) {
formIsValid = false;
formErrors["confirmPassword"] = "Does not match Password";
}
setErrors({ formErrors });
return formIsValid;
}
const handleSubmit = async e => {
e.preventDefault();
if (handleValidation()) {
const myToken = await changePassword(fields["password"]);
setToken(myToken);
} else {
alert("Form has errors.")
}
}
function handleChange(field, e) {
let myFields = fields;
myFields[field] = e.target.value;
setFields({ myFields });
}
return (
<div className="login wrapper fadeInDown">
<div className="login formContent ">
<h1 className="login centered">Change Password</h1>
<form onSubmit={handleSubmit}>
<div className="centered">
<span style={{ color: "red" }}>{errors["password"]}</span>
<input type="password" value={fields["password"] || ""} onChange={e => handleChange.bind(this, "password")} id="login" className="login fadeIn second" placeholder="Password" />
</div>
<div className="centered">
<span style={{ color: "red" }}>{errors["confirmPassword"]}</span>
<input type="password" value={fields["confirmPassword"] || ""} onChange={e => handleChange.bind(this, "confirmPassword")} id="login" className="login fadeIn third" placeholder="Confirm Password" />
</div>
<div className="centered">
<input type="submit" className="login fadeIn fourth" value="Submit" />
</div>
</form>
</div>
</div>
)
}
ChangePassword.propTypes = {
setToken: PropTypes.func.isRequired
}
The function loads, but I cannot type anything in Password
or Confirm Password
. For the life of me I can't figure out why. I am hoping a kind SO user will point out my error(s).
UPDATE
Just to be complete... Based on the comment by @evolutionxbox I changed my function changePassword
to:
async function changePassword(password) {
const response = await fetch(Config.apiUrl + "/api/Users/ChangePassword", {
method: "POST",
headers: headers,
body: JSON.stringify(password)
})
const json = await response.json();
console.log(json);
return json;
}
Upvotes: 0
Views: 140
Reputation: 9
function handleChange(field, e) {
let myFields = fields;
myFields[field] = e.target.value;
setFields({ myFields });
}
It is a mistake that most new people in react make. UseState is immutable and when you are saying
let myFields = fields;
You are copying a reference to your fields useState. Try destructuring, that will copy the value not reference
let myFields = [...fields];
But my advice would be to create 2 useStates one for password, another for confirmPassword, it will make your code clearer and wont mess up the states. Then change your input element in jsx from that
<input type="password" value={fields["password"] || ""} onChange={e => handleChange.bind(this, "password")} id="login" className="login fadeIn second" placeholder="Password" />
to that
<input type="password" value={fields["password"] || ""} onChange={e => setPassword(e.target.value)} id="login" className="login fadeIn second" placeholder="Password" />
Upvotes: 0
Reputation: 20584
As other commenters mentioned, you don't need to .bind
in a non-class context.
Change your onChange
event handlers from
onChange={e => handleChange.bind(this, "password")}
Add a name
attribute to your input
elements:
<input name="password" ... />
<input name="confirmPassword" ... />
to
onChange={handleChange}
Then you can update your handleChange
function to the following:
function handleChange(e) {
setFields({
...fields,
[e.target.name]: e.target.value
});
}
The above setState
has the added benefit of not mutating your existing state object
Upvotes: 3