I have a basic form with two inputs: email and confirmEmail, which updates the email address and also confirms if the new email address was typed correctly.
So far validation works also fine. Whenever email doesn't match with the confirmEmail or one of the inputs is empty, it will throw an error.
However, I want to put all this validation to the submit button, so that validation worked and errors are highlighted only once button is clicked, and update registeredEmail state if input value was valid.
Here is the code snippet and sandbox link.
import React, { useState } from "react";
function Form() {
const [registeredEmail, setRegisteredEmail] = useState("JohnDoe#gmail.com");
const [input, setInput] = useState({
email: "",
confirmEmail: ""
});
const [error, setError] = useState({
email: "",
confirmEmail: ""
});
const onInputChange = (e) => {
const { name, value } = e.target;
setInput((prev) => ({
...prev,
[name]: value
}));
validateInput(e);
};
const validateInput = (e) => {
let { name, value } = e.target;
setError((prev) => {
const stateObj = { ...prev, [name]: "" };
switch (name) {
case "email":
if (!value) {
stateObj[name] = "Please enter Email";
} else if (input.confirmEmail && value !== input.confirmEmail) {
stateObj["confirmEmail"] =
"Email and Confirm Email does not match.";
} else {
stateObj["confirmEmail"] = input.confirmEmail
? ""
: error.confirmEmail;
}
break;
case "confirmEmail":
if (!value) {
stateObj[name] = "Please enter Confirm Email.";
} else if (input.email && value !== input.email) {
stateObj[name] = "Email and Confirm Email does not match.";
}
break;
default:
break;
}
return stateObj;
});
};
const handleSubmit = (e) => {
e.preventDefault();
validateInput(e);
setRegisteredEmail(input.email);
};
return (
<>
<header>{registeredEmail}</header>
<form
style={{
display: "flex",
flexDirection: "column"
}}
>
<input
type="email"
name="email"
placeholder="address"
onChange={onInputChange}
value={input.email}
/>
{error.email && <span style={{ color: "red" }}>{error.email}</span>}
<input
onChange={onInputChange}
value={input.confirmEmail}
type="email"
name="confirmEmail"
placeholder="repeat address"
/>
{error.confirmEmail && (
<span style={{ color: "red" }}>{error.confirmEmail}</span>
)}
</form>
<button onClick={handleSubmit}>speichern</button>
</>
);
}
export default Form;
Any help will be appreciated
name is an attribute and needs function getAttribute(...) to be fetched.
Try this:
var name = e.target.getAttribute('name');
UPDATE
This won't work because the real problem is that you are checking inside the event of the button that submitted. So you don't have the inputs info and values. You should check the input state and validate those (Here you can set the errors). Then you can return a boolean to decide if the user can submit or not.
Try this:
const validateInput = () => {
if (input.email === "") {
setError({ ...error, email: "Please enter Email" });
return false;
}
if (input.email !== input.confirmEMail) {
setError({
...error,
confirmEmail: "Email and Confirm Email does not match."
});
return false;
}
// HERE YOU CAN ADD MORE VALIDATIONS LIKE ABOVE
return true;
};
const handleSubmit = (e) => {
e.preventDefault();
const isValid = validateInput();
if (isValid) {
//SubmitFunc()
}
};
You currently have your onInputChange handler run validateInput, which then sets the error. You may want to have it run validateInput only in your handleSubmit handler and only use onInputChange to handle state changes on keystrokes as you currently do.
Related
I have some front-end validation for formatting some inputs on a form. Currently my errors message print to the console. I would like it so that these error message became the innerHTML of a heading in the component.
I have already tried assigning the innerHTML with document.getElementByID but it hasnt worked. Ive additionally tried defining the error variable outside of scope or adding it between the h2 tags as {error}
I would like any suggestions to make these error messages display as text on the front-end rather than being printed to console as they are now.
The blank h2 element is the element im trying to target the error text towards.
import React, {useState} from 'react';
import {useNavigate} from 'react-router-dom';
import axios from 'axios';
import Helmet from 'react-helmet';
import Button from '#mui/material/Button';
export default function Register() {
const navigate = useNavigate();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const inputs = [
{
placeholder: 'First name',
setState: setFirstName
},
{
placeholder: 'Last name',
setState: setLastName
},
{
placeholder: 'Email',
setState: setEmail
},
{
placeholder: 'Enter a password',
setState: setPassword,
},
{
placeholder: 'Confirm password',
setState: setConfirmPassword,
},
]
//Insert into database api request
const insertRow = () => {
axios.post('/users/insert', {
firstName: firstName,
lastName: lastName,
email: email,
password: password,
})
};
//#region Validation
const atSymbol = '#';
//Checks for numbers in string
function containsNumber(str) {
return /[0-9]/.test(str);
}
//Checks for capital in string
function containsCapital(str) {
return /[A-Z]/.test(str);
}
const submitHandler = (e) => {
e.preventDefault(); //Prevents page refresh
let error = ''
//If no # symobol in email
if (!email.includes(atSymbol)) {
error = 'Please enter a valid email';
console.log(error);
return;
}
//If password doesn't contain a capital
if (!containsCapital(password)) {
error = 'Password must contain at least one uppercase letter';
console.log(error);
return;
}
//If password doesn't contain a number
if (!containsNumber(password)) {
error = 'Password must contain at least one number';
console.log(error);
return;
}
//If password is less than 8 characters
if (password.length < 8) {
error = 'Password must be at least 8 characters long';
console.log(error);
return;
}
//If passwords don't match
if (confirmPassword !== password) {
error = 'Passwords do not match';
console.log(error);
return;
}
//If all validation passes
insertRow();
navigate('/login');
}
//#endregion
return (
<>
<Helmet>
<title>Title | Register</title>
</Helmet>
<div className="pt-36 sm:pt-44 pb-20 md:pb-48 max-w-[1200px] mx-5 lg:mx-auto space-y-5">
<div className="bg-red-300 max-w-[500px] p-1 mx-auto">
<h2 className="text-lg text-red-900 font-semibold"></h2>
</div>
<h1 className="text-2xl font-semibold text-center">Register</h1>
<form onSubmit={submitHandler} className="flex flex-col space-y-5 max-w-[500px] mx-auto">
{inputs.map((items, index) => (
<input
key={index}
type="text"
className="border-2 border-black p-1"
placeholder={`${items.placeholder} *`}
required
onChange={(e) => {
items.setState(e.target.value);
}}
/>
))}
<Button
type="submit"
sx={{
border: '2px solid #000000',
color: '#000000',
marginLeft: 'auto',
marginRight: 'auto',
':hover': {
bgcolor: '#ffffff',
color: '#000000',
},
}}
>
Submit
</Button>
</form>
</div>
</>
)
}
You cannot do directly {error} as error is not defined as a state property in the component.
You have to declare it
const [error, setError] = useState('');
and whenever you set error you have to use setError
if (!email.includes(atSymbol)) {
setError('Please enter a valid email');
console.log(error);
return;
}
then in JSX you can use
{error.length>0 && <p>{error}</p>}
although it will be a common error for all the inputs
To keep track of the errors for every field you could take this approach.
An object for all the errors with the field name as object key. Update the state with the new error. Now this will not keep track of multiple errors for every field but will eventually pass all of them. Before insertRow check if there are any errors, if any return.
const [errors, setErrors] = useState({ email: "", password: "" });
const inputs = [
{
email: 'email', // key to get value from errors
placeholder: 'Email',
setState: setEmail
},
...
]
const submitHandler = (e) => {
e.preventDefault(); //Prevents page refresh
setErrors({ email: "", password: "" }); // reset the errors on every submit
//If no # symobol in email
if (!email.includes(atSymbol)) {
setErrors((prevError) => ({
...prevError,
email: "Please enter a valid email",
}));
// no return needed
}
// ... at the end before navigating check if there are errors
if (errors.email || errors.password) return;
//If all validation passes
insertRow();
navigate('/login');
};
The extra name prop comes into play when we want to display the errors. After every input we check if there is an error for that field and display it if any.
return (
...
{inputs.map((items, index) => (
<>
<input
key={index}
type="text"
className="border-2 border-black p-1"
placeholder={`${items.placeholder} *`}
required
onChange={(e) => {
items.setState(e.target.value);
}}
/>
{ errors[items.name] && <div>{errors[items.name]}</div>}
</>
))}
...
);
You cannot do directly {error} as error is not defined as a state property in the component.
You have to declare it
const [error, setError] = useState('');
and whenever you set error you have to use setError
if (!email.includes(atSymbol)) {
setError('Please enter a valid email');
console.log(error);
return;
}
then in JSX you can use
{error.length>0 && <p>{error}</p>}
although it will be a common error for all the inputs
for the solution of #kaneki21 i prefer this solution for JSX
{error?.length>0? <p>{error}</p>:null}
in case if error is "undefined" or "null"
So basically I've a Login form with two input fields (password, email) and a react-google-recaptcha. My use case is simple. Test if the submit button is disabled if the input fields are empty and recaptcha is not verified. Enable it only when input fields contain data and recaptcha is verified.
Below is the code that I wrote and I know I did something wrong with recaptcha verification in test file.
I've gone through existing answers in Stack Overflow for example this but facing problem implementing the same.
Login.tsx
import React, { useState } from "react";
import ReCAPTCHA from "react-google-recaptcha";
const Login = () => {
const [creds, setCreds] = useState({
email: "",
pw: "",
});
const [isCaptchaVerified, setIsCaptchaVerified] = useState(false);
const handleCaptchaChange = (value): void => {
if (value !== null) setIsCaptchaVerified(true);
else setIsCaptchaVerified(false);
};
return (
<div>
<input
data-testid="email-testid"
type="email"
name="email"
value={creds.email}
onChange={(e) => {
setCreds({
email: e.target.value,
pw: creds.pw,
});
}}
/>
<input
data-testid="pw-testid"
type="password"
name="password"
value={creds.pw}
onChange={(e) => {
setCreds({
pw: e.target.value,
email: creds.email,
});
}}
/>
<ReCAPTCHA
data-testid="login-recaptcha"
sitekey={siteKey}
onChange={handleCaptchaChange}
/>
<button data-testid="submit-testid" disabled={!isCaptchaVerified || !creds.pw ||
!creds.email}>
Submit
</button>
</div>
);
};
export default Login;
Login.test.tsx
test("test if button is disabled untill captcha is verified",()=> {
const loginRecaptcha = screen.findByTestId('login-recaptcha');
const emailField = screen.findByTestId('email-testid');
const pwField = screen.findByTestId('pw-testid');
const submitButton = screen.findByTestId('submit-testid');
expect(submitButton).toBeDisabled();
fireEvent.change(emailField, { target: { value: "user#test.com" } });
fireEvent.change(pwField, { target: { value: "user#1234" } });
fireEvent.click(loginRecaptcha);
expect(submitButton).not.toBeDisabled();
})
I am using email.js to send emails client side, and validator to validate the email and phone number. Everything works fine, except... I am trying to empty the input fields after a successful submission.
Here is what I have so far:
State management:
const formRef = useRef()
const [emailError, setEmailError] = useState('')
const [phoneError, setPhoneError] = useState('')
const [inputValues, setInputValues] = useState({email: "", phone: ""})
const handleOnChange = event => {
const { name, value } = event.target;
setInputValues({ ...inputValues, [name]: value });
validateEmail(inputValues.email)
validatePhone(inputValues.phone)
};
Validation and Submit handler:
const validateEmail = (email) => {
if (validator.isEmail(email)) {
setEmailError('Valid Email :)')
return true
} else {
setEmailError('Enter valid Email!')
return false
}
}
const validatePhone = (phone) => {
if (validator.isMobilePhone(phone)) {
setPhoneError('Valid Phone :)')
return true
} else {
setPhoneError('Enter valid Phone!')
return false
}
}
const handleSubmit = (e) => {
e.preventDefault()
const isValidEmail = validateEmail(e.target.email.value)
const isValidPhone = validatePhone(e.target.phone.value)
if(isValidEmail && isValidPhone){
console.log("if both inputs are true, on to submit")
setSentMessage(false)
//shouldnt this line empty out the current fields?
setInputValues({email: "", phone: ""})
} else {
console.log("one of the inputs is false, wont submit")
}
}
Form:
<form ref={formRef} onSubmit={handleSubmit} className={classes.contactPageInputs}>
<input placeholder='email' type="text" id="userEmail" name="email" onChange={(e) => handleOnChange(e)}></input>
<span style={{fontWeight: 'bold', color: 'red' }}>{emailError}</span>
<input placeholder='phone' id="userPhone" name="phone" onChange={(e) => handleOnChange(e)}></input> <br />
<span style={{fontWeight: 'bold', color: 'red' }}>{phoneError}</span>
<button className={classes.submitButton}>submit</button>
</form>
QUESTION:
How can I reset the input fields after submission?
Setting the form refs value to null worked.
Here is what I added to the handleSubmit function, after sending the email:
formRef.current[0].value = null
formRef.current[1].value = null
UPDATE
This si the better way. I added value={inputValues.user_email}, value={inputValues.user_phone}, value={inputValues.user_message} to each respective input field.
I need help with state management in my Chat-app.
Description:
I am attempting to display the username entered from the UserModal component to my ChatScreen component. I am using the onChange function inside UserModal and I am using a switch case statement to ensure form validation. Then setting the state with name as an array and assigning the value to the name like so:
UserModal.js
onChange = e => {
e.preventDefault();
const { name, value } = e.target;
let formError = this.state.formError;
switch (name) {
case 'userName':
formError.userName =
value.length < 3 ? 'minimum 3 characters required' : '';
break;
default:
}
this.setState({ formError, [name]: value }, () =>
console.log(this.state)
);
};
onSubmit function
onSubmit = (e) => {
e.preventDefault();
if (formValid(this.state)) {
Axios.post('http://localhost:5000/api/authenticate').then(
(res) => {
if (res.data.isSuccessful === true) {
alert('Your username is: ' + this.input.value);
this.close();
} else {
alert('Error Message: ' + res.data.ErrorMessage);
}
}
);
} else {
alert(
'Error message: Please enter a username with a minimum of 3 characters'
);
}
};
Form
<form action="Main.js" className="form-inside-input" onSubmit={this.onSubmit} noValidate>
<label>Username:</label>
<input
className={formError.userName.length > 0 ? "error" : null}
type="text"
placeholder="Enter username"
name="userName"
onChange={this.onChange}
ref={(input) => (this.input = input)}
noValidate
></input>
{formError.userName.length > 0 && <span>{formError.userName}</span>}
<Button
type="submit"
positive
icon="checkmark"
labelPosition="right"
content="Let me in!"
onClick={() => {
this.onSearch();
}}
/>
</form>;
Goal:
My goal is to take what the user puts in the username field in the modal and display "{userName} has joined the chat." where userName is replaced with the input of the user, in the ChatScreen.js component within the Semantic UI fragments.
Github Link: https://github.com/George-Jindo/chat-app
I have a React application where I have a form with an email and phone number fields. The textBoxes validates itself onBlur. When I have an error for email and it appears on screen, when I fix it and then click the submit button, the onBlur fires and clears the error but submit is not fired. I want the error to be cleared and submit to be fired after that since the submit button has been clicked
function SaveContactInfo () {
const dispatch = useDispatch()
const { email, phoneNumber } = useSelector(getContactInfo)
const [emailValue, setEmail] = useInput(email)
const [phone, setPhone] = useInput(phoneNumber)
const [emailError, setEmailError] = useState(null)
const [phoneError, setPhoneError] = useState(null)
const handlePhoneOnBlur = useCallback((target) => {
if (target.value) {
let number = phoneUtil.parse(target.value, 'US')
let valid = phoneUtil.isValidNumber(number)
if (!valid) {
if (!phoneError) {
setPhoneError(true)
target.focus()
}
} else {
setPhoneError(false)
}
} else {
setPhoneError(false)
}
}, [setPhoneError, phoneUtil, phoneError])
const handleEmailOnBlur = useCallback((target) => {
if (target.value) {
if (!target.checkValidity()) {
if (!emailError) {
setEmailError(true)
target.focus()
}
} else {
setEmailError(false)
}
} else {
setEmailError(false)
}
}, [setEmailError, emailError])
const handleSaveContactInfo = useCallback((e) => {
if (!emailError && !phoneError) {
dispatch(updateContactInfo(phone, emailValue))
}
}, [dispatch, emailValue, phone, phoneUtil, emailError, phoneError])
return (
<form className={classes.preferences} noValidate onSubmit={handleSaveContactInfo}>
<FormControl fullWidth className={classes.preference}>
<EmailInfo />
<TextInputField value={emailValue || ''} error={emailError} FormHelperTextProps={{ 'data-testid': 'emailError' }} helperText={emailError && 'Enter email address in format: yourname#example.com' variant='outlined' type='email' maxLength={100} onChange={setEmail} onBlur={handleEmailOnBlur} label='Email' />
</FormControl>
<FormControl fullWidth className={classes.preference}>
<PhoneInfo />
<TextInputField value={phone || ''} error={phoneError} helperText={phoneError && 'Enter phone number with area code and in format: 123-456-7890' variant='outlined' type='tel' onChange={setPhone} onBlur={handlePhoneOnBlur} label='Phone Number' />
</FormControl>
<div>
<PrimaryButton type='submit'>Submit</PrimaryButton>
</div>
</form>
)
}
export default SaveContactInfo
Update: I found that there is a known react issue https://github.com/facebook/react/issues/4210 where when an onBlur caused a shift in position of the submit button as there is a DOM rerender, the click is not registered and hence it fails. IN fact all buttons that shift position, their click is not registered. Any solution to that?