Pass error variable as inner html of element - javascript

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"

Related

How to verify a react-google-recaptcha v2 using jest while testing a React-Typescript App?

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

React.js: How to submit form if inputs are validated?

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.

Skipping validation before form submission react-hooks [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 10 months ago.
My form Submission would skip showing validation error message when submitting wrong input. How can I prevent submission when error message is present?
my code:
import React, { useState } from "react";
const App = () => {
const [formData, setFormData] = useState({ name: "" });
const [formError, setFormError] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);
const validate = () => {
const errors = {};
console.log("2. errors: ", errors); //<=== point 2.
if (formData.name.length === 0) {
errors.name = "name required";
}
console.log("3. errors: ", errors); //<=== point 3.
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmit(true);
console.log("1. formData: ", formData); //<=== point 1.
setFormError(validate(formData));
console.log("4. formError: ", formError); //<=== point 4.
// This is true only if formError is empty object
if (
Object.keys(formError).length === 0 &&
formError.constructor === Object
) {
console.log("5. sending data: ", formData.name); //<=== point 5.
// Sending data to API here
setIsSubmit(false);
setFormSubmitted(true);
return;
}
setIsSubmit(false);
return;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<>
{formSubmitted ? (
<p>Thank you</p> //<=== Thank you prhase
) : (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
{formError.name && <p>{formError.name}</p>}
{isSubmit ? (
<p>Sending...</p>
) : (
<input type="submit" value="Submit" />
)}
</form>
)}
</>
);
};
export default App;
console when submitting empty input (wrong input):
1. formData: {name: ''}
2. errors: {}
3. errors: {name: 'name required'}
4. formError: {}
5. sending data:
console when submitting with input "test" (right input):
1. formData: {name: 'test'}
2. errors: {}
3. errors: {}
4. formError: {}
5. sending data: test
When I submit a right input, the form would act as I wanted.
submit right input -> input validation -> no error found -> send data -> show "Thank you" phrase
But if I submit wrong input.
submit wrong input -> input validation -> error found -> append error message -> but still return empty object (to formError) -> send empty input -> show "Thank you" phrase
The problem is at point 4, the validate() function wont return the error message. On my actual file with API, while submitting wrong input it will show the error message (e.g. formError.name) (takes 1 to 2 seconds to send data to CMS) but the submission will still goes thru and show "thank you" phrase. How can I fix this? Thank you.
State update doesn't happen synchronously.
You can extract the error in a variable (named errors in the example below) and do the conditional logic based on that instead of the state.
import React, { useState } from "react";
const App = () => {
const [formData, setFormData] = useState({ name: "" });
const [formError, setFormError] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);
const validate = () => {
const errors = {};
if (formData.name.length === 0) {
errors.name = "name required";
}
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmit(true);
const errors = validate(formData); // extracting here
setFormError(errors); // setting state using the extracted value
// validating using the extracted value
if (Object.keys(errors).length === 0 && errors.constructor === Object) {
// Sending data to API here
setIsSubmit(false);
setFormSubmitted(true);
return;
}
setIsSubmit(false);
return;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<>
{formSubmitted ? (
<p>Thank you</p> //<=== Thank you prhase
) : (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
{formError.name && <p>{formError.name}</p>}
{isSubmit ? (
<p>Sending...</p>
) : (
<input type="submit" value="Submit" />
)}
</form>
)}
</>
);
};
export default App;

Input wont reset after form submission?

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.

onSubmit not firing when Submit Button is clicked because onBlur is fired

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?

Categories

Resources