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>;
}
}
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 very new to JS, React and am not able to pass state objects from React Component function to a external function.
This is my FormPage parent component method 'formSubmit ' from where i am making the call
import { formValidate } from './FormHelpers';
class FormPage extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: {
userName: "", userAttributes: []
},
errors: {}
}
};
formSubmit = (e) => {
e.preventDefault();
const formErrors = formValidate(this.state.fields);
....
.....
}
Below is my function formValidate defined in FormHelpers.js file:
export const formValidate = ({fieldsIn}) => {
let errors = {};
let formIsValid = true;
let fields = fieldsIn;
if (!fields["userName"]) {
formIsValid = false;
errors["userName"] = "Please enter metadata";
}
if (typeof fields["userName"] !== "undefined") {
if (!fields["userName"].match(/^[a-zA-Z ]*$/)) {
formIsValid = false;
errors["userName"] = "Please enter alphabet char only";
}
}
let formErrors = {
errors: errors, formIsValid: formIsValid
};
return formErrors;
}
I would like to call formValidate() function with state.fields as argument and return formErrors as object back to React.
While executing, am getting below error in FormHelpers.js
Uncaught TypeError: Cannot read property 'userName' of undefined
I don't seem to understand my mistake here. Can you please guide me on this.
Thanks in advance
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 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!');
}
};
I have the weirdest bug I have ever encountered. I am using Axios and Vee-Validate in my Vue project and from my api I get an error. So withing axios I have a catch.
example:
this.$http.post('v1/auth/register', {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
password:this.password
}).then((response) => {
this.registration_card = 2;
}).catch((error) => {
if(error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
this.loading = false;
console.log(input.errors);
console.log(this.loading);
}
});
Now here comes the weird part. With this code:
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
this.loading = false;
the input.errors is still empty and error wil not be displayed. BUT when i do this:
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
// this.loading = false;
So this.loading will NOT get set, then the error will get set and displayed in my view.
But I want this.loading still be false because I want my loading icon not be displayed. Anyone have a explanation about this.
EDIT: More code
methods: {
register: function () {
let anyError = false;
this.$validate(this, ['first_name', 'last_name', 'phone', 'email', 'password'], function (value, last_item) {
this.loading = true;
if (value === false) {
anyError = true;
}
if (anyError || !last_item) {
return;
}
this.$http.post('v1/auth/register', {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
password: this.password
}).then((response) => {
this.registration_card = 2;
}).catch((error) => {
if (error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({field: 'email', msg: 'email already is use'});
this.loadingTest = false;
console.log(input.errors);
console.log(this.loadingTest);
}
});
}.bind(this));
},
}
this.$validate does this:
export default function(scope, arrayOfValues, callback) {
let total = arrayOfValues.length - 1;
let last_item = false;
arrayOfValues.forEach(function(value, index) {
let input = scope.$refs[value].$children[0];
input.$validator.validate().then(value => callback(value, total === index, index));
});
}
I do this because i have custom input components
EDIT: this is where i am using loading:
<j-button label="Register" :loading="loading" #click.native="register"/>
And button coomponent is:
<template>
<button type="button">
<span v-if="!loading">{{label}}</span>
<loading v-if="loading"/>
</button>
</template>
<script>
import loading from 'vue-loading-spinner/src/components/Circle'
export default {
name: 'j-button',
props: [
'label',
'loading'
],
components: {
loading
}
}
</script>
EDIT: Even more code!!!!!
My j-input component
<template>
<div>
<label v-bind:class="{ 'active': (newValue.length > 0)}">{{label}}</label>
<input v-bind:class="{ 'error': (errors.has(name))}" type="text" :name="name" v-validate="rules" :placeholder="label" v-model="newValue" v-on:input="updateValue()" ref="input">
<span v-if="errors.has(name)">{{errors.first(name)}}</span>
</div>
</template>
<script>
export default {
name: 'j-text',
inject: ['$validator'],
props: [
'label',
'name',
'rules',
'value',
],
data() {
return {
newValue: ''
}
},
created() {
this.newValue = this.value;
this.updateValue();
},
methods: {
updateValue: function () {
this.$emit('input', this.newValue);
},
}
}
</script>
So i have found the issue and it is still very strange why. I will make another question for this. Its about my j-button component:
<template>
<button type="button">
<span v-if="!loading">{{label}}</span>
<loading v-if="loading"/>
</button>
</template>
<script>
import loading from 'vue-loading-spinner/src/components/Circle'
export default {
name: 'jellow-button',
props: [
'label',
'loading'
],
components: {
loading
}
}
</script>
To fix this weird issue I had to change this:
<loading v-if="loading"/>
To:
<loading v-show="loading"/>
If I changed this, then the error will be loaded and the button loading icon will be turned off doing this in my catch:
}).catch(error => {
if(error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({field: 'email', msg: 'email already in use'});
this.loading = false;
}
});
But again. If I do the v-if instead of the v-show in my button then the error will not be showing. Very strange. I will create another question and I hope I get a answer on that.
This is very simple. Only reference change refreshes Vue view.
When you do this:
new Vue({
data: ['property'],
method: {
change() {
this.property = "yes"; // will get refreshed
}
}
});
The view gets refreshed (changes are displayed). But when you change the object's field reference (not the object itself) or when you call a method on it, it won't get refreshed.
change() {
this.property.field = "yes"; // won't get refreshed
this.property.mtehod("yes"); // won't get refreshed
}
Only some certain methods (like array.push()) are tweaked by Vue.js to recognize that those methods get view refreshed. If you want to make it work you need to call this.$forceUpdate() or use Vue.set() to change vales.
So when you add your errors, the view won't get refreshed, only when you change your data property (loading) the view recognize that data value changed and refreshed your view.
Please read Reactivity in Depth, especially chapter "How Changes Are Tracked". Please see which ways of setting data are reactive and which aren't.