I'm working on a React project and implementing email validation and setting the state to true when it doesn't pass and false when it does. Validation part works, but getting undefined state on second onSubmit.
A bit more detail: I'm checking the state onChange and onSubmit. onChange seems to work as expected. onSubmit does work on the first click/submit but the very next click/submit, it changes the state to 'undefined' and I have no idea why.
Best to view my codepen and start filling in the email field and checking the console as I'm logging the state.
Here's a snippet of the code:
this.state = {
inputs: {
name: '',
email: '',
message: '',
},
show: true,
errors: {
name: false,
email: false,
message: false,
},
};
validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
email: emailInput,
},
errors: {
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
console.log('errors.email onChange = ' + this.state.errors.email);
};
So, why is this happening? and how can I solve?
You have missed the else condition when the field is not empty. that will remove the error object key from state, that is the one gives you the undefined error.
rewrite the handleSubmit function like this.
handleSubmit = (e, slackPost) => {
e.preventDefault();
console.log('errors.email onClick = ' + this.state.errors.email);
let inputFields = document.getElementsByClassName('form-input');
let invalidEmailMessage = document.querySelector('#invalid-email-message');
let failMessage = document.querySelector('#fail-message');
let failMessageBox = document.querySelector('.alert-fail');
// empty array to house empty field names
const emptyFieldNames = [];
// empty object to house input state
let errors = {};
// loop through input fields...
for (var i = 0; i < inputFields.length; i++) {
if (inputFields[i].value === '') {
let inputName = inputFields[i].name;
// add name to new array
emptyFieldNames.push(inputFields[i].getAttribute('name'));
// add input name and value of true to new object
errors[inputName] = true;
failMessageBox.style.display = 'block';
} else {
let inputName = inputFields[i].name;
errors[inputName] = false;
}
}
debugger;
this.setState({ errors });
if (emptyFieldNames.length > 0) {
failMessage.innerHTML =
'Please complete the following field(s): ' + emptyFieldNames.join(', ');
} else if (this.state.errors.email === true) {
invalidEmailMessage.innerHTML = 'Please enter a valid email';
} else {
console.log('For Submitted!');
}
};
Related
I am validating form from server side. once I get error message, I wants to show error message on respective textbox field's error message
client side Object
const formFields = {
firstName: {
helperText: '',
error: false
},
lastName: {
helperText: '',
error: false
},
emailID: {
helperText: '',
error: false
},
phoneNo: {
helperText: '',
error: false
},
password: {
helperText: '',
error: false
},
confirmPassword: {
helperText: '',
error: false
}
}
Server side response object after validation
const responseError = errorData.response.data.errors //below object is the response
//{
//"LastName": ["Enter LastName"],
//"FirstName": ["Enter FirstName"],
//"ConfirmPassword": ["Enter Confirm Password","confirm password do not match"]
//}
useState
const [inpValues, setInpValues] = useState(formFields)
Conditions to update
if ClientSideObj.key === responseObj.key then setInpValues of error and helperText field
const responseError = errorData.response.data.errors
console.log(responseError)
var FormFieldName = ""
for (keys in formFields) {
console.log('FormField keys = ' + keys)
for (var errorKeys in responseError) {
if (keys.toLowerCase() === errorKeys.toLowerCase()) {
console.log('* MATCHED FIELDS = ' + errorKeys)
//Matched 3 fields(LastName,FirstName,ConfirmPassword) successfully
FormFieldName = keys
setInpValues(prevInpValues => ({
...prevInpValues,
[FormFieldName]: {
...prevInpValues[FormFieldName],
error: true,
helperText: responseError[errorKeys]
}
})
)
console.table(inpValues)
}
}
}
Note
I go through this stack overflow already, then I passed previousState values also. still result same.
It's updating only the last for loop condition value
If the response.error object has return only one field, then it's updating that one
It's updating only the last for loop condition value
Javascript infamous Loop issue?
What is the difference between "let" and "var"?
as Miguel Hidalgo states, you should make all changes in one update:
const responseError = errorData.response.data.errors
console.log(responseError)
setInpValues(state => {
const newState = { ...state };
for (let errorKey in responseError) {
for (let formField in formFields) {
// this would be so much simpler if your properties would be in the correct case and you'd not have to do this dance with `.toLowerCase()`
if (formField.toLowerCase() !== errorKey.toLowerCase()) {
continue;
}
newState[formField] = {
...newState[formField],
error: true,
helperText
}
}
}
return newState
});
If errorKey and fieldName would be identical and you'd not have to match them case insensitive you could write this:
const responseError = errorData.response.data.errors
console.log(responseError)
setInpValues(state => {
const newState = { ...state };
for (let formField in responseError) {
newState[formField] = {
...newState[formField],
error: true,
helperText: responseError[formField]
}
}
return newState
});
What you should do in order to avoid unnecessary extra renders its to loop inside setState callback:
setInpValues(prevInpValues => {
for (const formKey in formFields) {
for (const errorKey in responseError) {
if (formKey.toLowerCase() === errorKey.toLowerCase()) {
prevInpValues[formKey] = {
...prevInpValues[formKey],
error: true,
helperText: responseError[errorKey]
}
}
}
}
return {...prevInpValues}
}
I am trying to get my "submit" button to be enabled or disabled based on the validity of the email that is passed. However, the RegExp expression I am using is not reading the string properly. I've tried testing my isWorking() function with just email.length > 0 to make sure that whatever is being passed to isWorking() is indeed a string, and we are good there but RegExp is always returning false regardless of the input it receives. I'm using React without JSX in this app. Any help at all would be deeply appreciated. Thank you so much!
const validEmailRegex = RegExp(/^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
class Signup extends React.Component {
constructor() {
super();
this.state = {
email: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
}
isWorking () {
const email = event.target;
//if (email.length > 0) {
// return false;
// }
// return true;
if (validEmailRegex.test(email) === true) {
return false;
}
return true;
}
handleSubmit(event) {
event.preventDefault();
if (!event.target.checkValidity()) {
this.setState({
invalid: true,
displayErrors: true,
});
return;
}
const form = event.target;
const data = new FormData(form);
for (let name of data.keys()) {
const input = form.elements[name];
const parserName = input.dataset.parse;
console.log('parser name is', parserName);
if (parserName) {
const parsedValue = inputParsers[parserName](data.get(name));
data.set(name, parsedValue);
}
}
this.setState({
res: stringifyFormData(data),
invalid: false,
displayErrors: false,
});
}
render() {
const { res, invalid, displayErrors } = this.state;
//pass data to the button for disabling or not
const isEnabled = this.isWorking();
return (
React.createElement("div", { className: "container" },
React.createElement("div", { className: "row" },
React.createElement("form", { onSubmit: this.handleSubmit, noValidate: true, className: displayErrors ? 'displayErrors' : '' },
React.createElement("input", { className: "form-control", name: "formEmail", id: "formEmail", type: "email", placeholder: "email"}),
),
React.createElement("span", { className: "span"},
React.createElement("fieldset", { className: "form-group" },
React.createElement(Link, { className: "nav-link", activeClassName: "nav-link-active", to: "/contact" },
React.createElement("button", { className: "button1", disabled: isEnabled, type: "button"}, "next")
),
)
),
),
)
);
}
}
Your isWorking() does not receive event from anywhere. Also, event.target will be an HTML element and definitely not an input value. For a form, you do event.target.elements["<name_of_input>"] (here, name of input if formEmail) to get input value.
In my React app, I'm getting this error during onChange event with my email input field:
Warning: A component is changing a controlled input of
type text to be uncontrolled. Input elements should not switch from
controlled to uncontrolled (or vice versa).
Here's the onChange block that's causing this warning; The error goes away if I remove the first if block but of course I need it there for email validation.
validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
email: emailInput,
},
errors: {
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
};
State:
constructor() {
super();
this.state = {
inputs: {
name: '',
email: '',
message: '',
},
phone: '',
show: true,
errors: {
name: false,
email: false,
message: false,
},
};
}
How do I keep my current code and address the warning?
You need to spread the existing/previous state in the if-block. You likely have other input tags that were initially connected to the input-state object which looks like:
inputs: {
name: "",
email: "",
message: ""
}
<input value={this.state.input.name} name="name"/>
<input value={this.state.input.email} name="email"/>
<input value={this.state.input.message} name="message"/>
but when you used this.setState() in your posted code, the connection is lost. You are setting the inputs state to an object with a single property of email:
inputs: {
email: "valueFromEventTarget"
}
What you need to do is spread the existing state so you don't lose the other key/value pairs in the input object: Update your handleChange() function to this:
handleOnChange = e => {
const { name, value } = e.target;
const emailInput = e.target.value;
const emailValid = this.validateEmail(emailInput);
if (name === 'email') {
this.setState({
inputs: {
...this.state.inputs,
email: emailInput,
},
errors: {
...this.state.errors,
email: !emailValid,
},
});
} else {
this.setState({
inputs: {
...this.state.inputs,
[name]: value,
},
errors: {
...this.state.errors,
[name]: false,
},
});
}
};
i'm trying to traverse an Array and perform some operation. But cann't access the values as i want to.
inputObj.map(el => {
const msg = this.state.validation[el.id].message;
const msg2 = this.state.validation['name'].message;
})
Here, el.id can be name, dob or address. When i use this.state.validation[el.id].message;, it shows TypeError: Cannot read property 'message' of undefined. But if i hardcode it like this.state.validation['name'].message;, it works fine. when comparing both el.id and 'name', they have same datatype and same value. So, why having problem when using el.id instead of hardcoding it.
NB: i'm using reactjs.
Edit:
this.state:
this.state = {
super(props);
this.validator = new FormValidator([
{
field: 'name',
method: 'isEmpty',
validWhen: false,
message: 'Name is required'
},
...
]);
orderForm: {
name: {
elementType: 'input',
elementConfig: ''
},
...
}
validation: this.validator.setValid() // it will be the updated upon submitting form on submitHandler by calling validate() from FormValidator
}
inputObj:
const inputObj= [];
for(let key in this.state.orderForm){
inputObj.push({
id : key,
config: this.state.orderForm[key]
});
}
FormValidator
import validator from 'validator';
class FormValidator {
constructor(rules){
this.rules = rules;
}
setValid(){
const validation = {};
this.rules.map(rule => (
validation[rule.field] = {isValid: true, message: ''}
));
return {isValid: true, ...validation};
}
validate(form){
let validation = this.setValid();
this.rules.forEach( rule => {
if (validation[rule.field].isValid){
const field = form[rule.field].toString();
const args = rule.args || [];
const validationMethod = typeof rule.method === 'string' ?
validator[rule.method] : rule.method;
if (validationMethod(field, ...args, form) !== rule.validWhen){
validation[rule.field] = {isValid: false, message: rule.message};
validation.isValid = false;
}
}
});
return validation;
}
}
export default FormValidator;
You can check if this.state.validation[el.id] is defined before getting message key.
Like that you can't get fatal error.
inputObj.map(el => {
this.state.validation[el.id] && (
const msg = this.state.validation[el.id].message
);
})
I am having an issue resetting the errorText to it's original state. Every time the form is submitted with an error(s) it adds all of the errors to the end even if they are from a previous submit.
1st click on blank form // I expect this result every time.
"Errors: Email is invalid. Password is invalid."
2nd click on blank form
"Errors: Email is invalid. Password is invalid. Email is invalid. Password is invalid."
Code Snippet
class LoginForm extends Component {
constructor(props) {
super(props)
this.state = {
details: {
email: '',
password: '',
},
hasError: false,
errorText: 'Errors: \n',
}
}
render() {
let { hasError, errorText } = this.state
const { LogUserIn } = this.props
const onTapLogin = e => {
// broken?
if (hasError) {
this.setState({
hasError: false,
errorText: 'Errors: \n',
})
}
if (!check.emailValid(e.email)){
this.setState({
hasError: true,
errorText: errorText += "\n - Email address is invalid. "
})
}
if (!check.passwordValid(e.password)){
this.setState({
hasError: true,
errorText: errorText += "\n- Password is invalid. "
})
}
if (!hasError){
LogUserIn(e)
}
}
return (
<div {...cssLoginFormContainer}>
<div {...cssLoginFormHeader}>SIGN IN</div>
<div {...(hasError ? cssErrorText : cssErrorText_hide)}>
{errorText}
</div>
...
// the form.
I would take a different approach here for displaying errors, i.e. would implement error messages as separate values on your state:
this.state = {
...,
errorEmail: '',
errorPassword: ''
}
Set the state (for specific error you have):
this.setState({
hasError: true,
errorEmail: "\n - Email address is invalid. "
})
And your errorText can be extracted in separate function to return your errors' text:
function errorText() {
return `Errors: ${this.state.errorEmail} ${this.state.errorPassword}`
}
Note: you could also nest your errors under single errors object like so:
this.state = {
...,
errors = { email: '', password: '' }
}
In this case, watch out for the state update (since it is nested). More info: How to update a nested state in React
You have two problems here: first you're mutating the state using += to concatenate the strings. You probably want to use only +.
State should only be changed using setState. When you do errorText = erroText+="whatever" you're changing the string in place. Try the following code on your console:
x = "hello"
y = x+=" world"
x
y
You'll see you changed x while trying to set y to a new value
if you do
x = "hello"
y = x+ " world"
x
y
You'll see only y changes
Second, you're using setState multiple times in a single function, which can lead to race conditions
You should try setting state only once at the end, keep the string and the boolean in variables and use them to set the state
Render function should not include any side-effect, so it's better to wrtie onTapLogin function outside render function.
I don't see the point of setState multiple times, since setState may trigger React lifecycle and it will NOT execute immediately (It's an async function).
I rearranged your code to fix the issues, but the code you got didn't call onTapLogin function, so you might need change it to fit your actually code:
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
details: {
email: '',
password: '',
},
hasError: false,
errorText: null,
};
}
onTapLogin = e => {
let errorText = 'Errors: \n';
let hasError = false;
if (!check.emailValid(e.email)) {
errorText += 'Email address is invalid.\n';
hasError = true;
}
if (!check.passwordValid(e.password)) {
errorText += 'Password is invalid.\n';
hasError = true;
}
if (!hasError) {
this.logUserIn(e);
} else {
this.setState({
errorText,
hasError,
});
}
};
logUserIn = e => {}
render() {
const { hasError, errorText } = this.state;
const { LogUserIn } = this.props;
return <div>{hasError && <p>{errorText}</p>}</div>;
}
}