State value not updating on form validation - javascript

Okay so breaking down my code into chunks I have the following in HTML:
<input
type="text"
name="email"
id="email"
autoComplete="email"
onChange={(e) => {validateField(e.target)}}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
/>
<FormFieldError
Field='email'
FormFieldErrors={formFieldErrors}
/>
The validateField function looks like this:
const validateField = (field) => {
// console.log('validatefield', field.id, field.value)
switch(field.id) {
case 'email':
const pattern = /[a-zA-Z0-9]+[\.]?([a-zA-Z0-9]+)?[\#][a-z]{3,9}[\.][a-z]{2,5}/g
const result = pattern.test(field.value)
if (result !== true) {
setFormFieldErrors({...formFieldErrors, email: 'Please enter a valid email address'})
console.log('Please enter a valid email address')
} else {
setFormFieldErrors({...formFieldErrors, email: null})
}
break
}
}
The FormFieldError function looks like this:
function FormFieldError({ Field, FormFieldErrors }) {
let message = FormFieldErrors[Field] ? FormFieldErrors[Field] : null
return (
<>
<div className="text-red-500 text-xs text-bold">{(message)}</div>
</>
)
}
Now, when in the onSubmit of the form I got to the following function:
const submitNewRegistration = async (event) => {
event.preventDefault()
Array.prototype.forEach.call(event.target.elements, (element) => {
validateField(element);
})
let errors = Object.keys(formFieldErrors).some(key => key !== null)
console.log(errors)
}
I am declaring formFieldErrors in my state like this:
const [formFieldErrors, setFormFieldErrors] = useState({})
When I am changing a field the onChange function works perfectly for each input, and if the input is wrong then the message below the input shows up thanks to the formFieldErrors displays the message underneath the input. However, when I call validateInput from my submitNewRegistration function, setFormFieldErrors doesnt seem to be called. In fact, even if I put setFormFieldErrors in to submitNewRegistration it doesnt seem to change the state of formFieldErrors. So when I am trying to validate the form just before submitting it I do not get any errors.
Can anyone explain to me why its working with the onChange method and now when I call it from submitNewRegistration ??

It's not that setFormFieldErrors isn't called, the issue is you are trying to rely on the state value before the state has been updated. When you call setState the state doesn't update immediately. This is because setState is asynchronous, essentially a promise which resolves upon the next render of your component.
So to solve this, you need to strip your validation logic outside of the state update. In other words:
const validateField = (field) => {
// console.log('validatefield', field.id, field.value)
switch(field.id) {
case 'email':
const pattern = /[a-zA-Z0-9]+[\.]?([a-zA-Z0-9]+)?[\#][a-z]{3,9}[\.][a-z]{2,5}/g
const result = pattern.test(field.value)
if (result !== true) {
return {email: 'Please enter a valid email address'}
console.log('Please enter a valid email address')
} else {
return {email: null}
}
break
}
Then in your html:
<input
type="text"
name="email"
id="email"
autoComplete="email"
onChange={(e) => {
setFormFieldErrors({...formFieldErrors, ...validateField(e.target)})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
/>
<FormFieldError
Field='email'
FormFieldErrors={formFieldErrors}
/>
And finally in the submit:
const submitNewRegistration = async (event) => {
event.preventDefault()
let formErrors = formFieldErrors;;
Array.prototype.forEach.call(event.target.elements, (element) => {
formErrors = {...formErrors, ...validateField(element)};
})
setFormFieldErrors(formErrors)
let errors = Object.keys(formErrors).some(key => key !== null)
console.log(errors)
}

Related

Input Handle Change

I want to update the text whatever users types in the input field and then join that text with another text (i.e ".com" in this example). So somehow I managed to join the extension with the user's input text but when the input field is empty the extension is still showing. Can someone help me to remove that extension text when the input field is empty?
Here's my code
import React, { useState } from "react";
import Check from "./Check";
export default function App() {
const [inputValue, setInputValue] = useState("");
const [inputExtension, setInputExtension] = useState("");
const handleChange = (e) => {
setInputValue(e.target.value);
if (setInputValue == inputValue) {
setInputExtension(inputExtension);
}
if (inputValue == inputValue) {
setInputExtension(".com");
}
};
return (
<>
<h1 className="text-[2.1rem] font-semibold text-black">
Find Your Perfect Domain
</h1>
<form>
<label
for="default-search"
class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"
>
Search
</label>
<div class="relative">
<input
type="search"
id="in"
value={inputValue}
onChange={handleChange}
class="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
placeholder="Search for domains..."
/>
</div>
</form>
<Check domain={inputValue} extension={inputExtension} />
<Check domain={inputValue} extension={inputExtension} />
</>
);
}
Here you have the code
if (inputValue == inputValue) {
setInputExtension(".com");
}
Change this to
if(inputValue.length > 0) {
setInputExtension(".com");
}
If you are passing both inputValue and inputExtension as props to the Check component then it should be trivial to just check the inputValue length, i.e. use the fact that empty strings are falsey, and conditionally append the extension value.
Example:
const value = ({
inputValue,
inputExtension = ".com"
}) => inputValue + (inputValue ? inputExtension : "");
console.log({ value: value({ inputValue: "" }) }); // ""
console.log({ value: value({ inputValue: "someValue" })}); // "someValue.com"
Code:
const Check = ({ inputValue = "", inputExtension = "" }) => {
const value = inputValue + (inputValue ? inputExtension : "");
...
};

fullname,email,password,setError,setError,fullname,error is not defined react js

As you can see I am trying to apply validation to registration form that is, when user enters values in input fields he should be able to see the validations. Can anyone tell me where i am going wrong here. I am using functional components and the form should validate before submitting.
import React, {useState, useRef, useEffect} from 'react';
const Home = () => {
const [ userRegistration, setUserRegistration ] = useState ({
fullname:"", email:"", phone:"", password:""
});
// States for checking the errors
const [submitted, setSubmitted] = useState(false);
const [record, setRecord] = useState ([]);
const handleInput = (e) => {
setUserRegistration ({...userRegistration, [e.target.name] : e.target.value});
setSubmitted(false);
}
Here I am struggling
// Handling the form submission
const handleSubmit = (e) => {
e.preventDefault();
// const newRecord = {...userRegistration, id: new Date().getTime().toString()};
// setRecord = {...record, newRecord};
// setUserRegistration ({fullname:"", email: "", phone:"", password:""});
{
if (fullname === '' || email === '' || password === '') {
setError(true);
} else {
setSubmitted(true);
setError(false);
}
};
}
// Showing success message
const successMessage = () => {
return (
<div
className="success"
style={{
display: submitted ? '' : 'none',
}}>
<h1>User {fullname} successfully registered!!</h1>
</div>
);
};
// Showing error message if error is true
const errorMessage = () => {
return (
<div
className="error"
style={{
display: error ? '' : 'none',
}}>
<h1>Please enter all the fields</h1>
</div>
);
};
// const handleSubmit = (e) => {
// e.preventDefault();
// const newRecord = {...userRegistration, id: new Date().getTime().toString()};
// setRecord = {...record, newRecord};
// setUserRegistration ({fullname:"", email: "", phone:"", password:""});
// }
return (
<>
{/* Calling to the methods */}
<div className="messages">
{errorMessage()}
{successMessage()}
</div>
<form>
<div>
<label htmlFor="fullname">Fullname</label>
<input type="text" autocomplete="off" onChange={handleInput} value={userRegistration.fullname} name="fullname" id="fullname" />
</div>
<div>
<label htmlFor="email">email</label>
<input type="text" autocomplete="off" onChange={handleInput} value={userRegistration.email} name="fullname" id="fullname" />
</div>
<div>
<label htmlFor="phone">phone</label>
<input type="text" autocomplete="off" onChange={handleInput} value={userRegistration.phone} name="fullname" id="fullname" />
</div>
<div>
<label htmlFor="password">password</label>
<input type="text" autocomplete="off" onChange={handleInput} value={userRegistration.password} name="fullname" id="fullname" />
</div>
<button onClick={handleSubmit}>SUBMIT</button>
</form>
<div>
{
record.map ((curElem) => {
const {id, fullname, email, phone, password} =curElem
return(
<div key={id} >
<p>{fullname}</p>
<p>{email}</p>
<p>{phone}</p>
<p>{password}</p>
</div>
)
}
)
}
</div>
</>
)}
export default Home;
The error is:
src\component\Home.js
Line 31:9: 'fullname' is not defined no-undef
Line 31:28: 'email' is not defined no-undef
Line 31:44: 'password' is not defined no-undef
Line 32:7: 'setError' is not defined no-undef
Line 35:7: 'setError' is not defined no-undef
Line 47:19: 'fullname' is not defined no-undef
Line 58:20: 'error' is not defined no-undef
You havent added state variable for error. (error variable is used in errorMessage function definition)
in successMessage function definition you have to give {userRegistration.fullname} instead of {fullname}
In handleSubmit you are using fullname, email, password but they are not destructured from userRegistration. So you have to give either userRegistration.fullname, etc. like mentioned in 2. or destructure the properties from the userRegistration object before the if condition like
const {fullname,email,password} = userRegistration;
All the inputs are having same 'name' attribute, change to the respective key for it to work correctly.
There are quite a few adjustments that you need to make.
You do not have Error handling useState. You setError state, but you do not have it initiated at the top of the Home component.
From the beginning, you set setSubmitted to false, so unless you change that state to 'true', you do not need to set it to false since it is already false.
Your Submit function receives data on onSubmit event that you should JSON.stringify in order to use it further, but you just change states in your function. Or you can push that form data into the state too.
Your validation is super duper basic just checking if it is an empty string. I would suggest writing up more validations than that.
In successMessage function definition you have to give {userRegistration.fullname} instead of {fullname} .
Your input names are literally the same for all inputs. They must be different, just like "id"s. Your ids are also the same. Your form object keys will be named after "name" of each input field. Change that.
Lastly, use either Formik or Yup or React Hook Form that will assist you tremendously with this process and make it smoother and simplier. For example, read documentation on React Hook Form here(it is quite easy documentation)
https://react-hook-form.com/

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

Form validation at server side. How to show validation message in form? (React / Formik / Antd)

For example form with only email field:
const RegistrationForm = (props) => {
const { values, touched, errors, handleChange, handleBlur, handleSubmit, status } = props;
const emailFieldHelp = () => {
const { touched, errors, status, setStatus } = props;
console.log('status: ', status);
if (touched.email && errors.email) {
return errors.email;
}
if (status && !status.isUserAdded) {
setStatus({"isUserAdded": false});
return "User already exist";
}
return null;
};
return (
<Form onFinish={handleSubmit}>
<Form.Item
help={emailFieldHelp()}
validateStatus={setFieldValidateStatus('email')}
label="E-mail"
name="email"
hasFeedback={touched.email && values.email !== ''}
>
<Input
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
};
const RegFormView = withFormik({
validationSchema,
mapPropsToValues: () => ({
email: ''
}),
handleSubmit: async (values, { setErrors, setSubmitting, setStatus }) => {
await userService.createUser('/signup/', values)
.then(response => {
setStatus('');
const status = (response.isAdded)
? {isUserAdded: true}
: {isUserAdded: false};
setStatus(status);
setSubmitting(false);
}, (error) => {
setErrors(error);
});
},
})(RegistrationForm);
When I send form and validate it on sever side, I return 'isAdded' = false or true. Than I set status to {isUserAdded: true} or false if user not added. and I absolutely have no idea how to show this message in form under email field and keep form working. Now I can show message but than I cant send form second time becouse of status already set to {isUserAdded: true}. Do I need somehow to change status when user change email field? but I can't do it becouse of the formik.
here onChange={handleChange} I can pass only handleChange and can't run my function or I can run my function like this onChange={myFunc()} but than its seems impossible to pass handleChange to Formik. (I dont need call it like this handleChange(e) its not work! i need somehow pass it to Formik) I'm total stuck. If you have knowledge in react plz help.
And if you know some examples how to show server side validation messages in react form, links to this examples might be helpful. ty.

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