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?
Related
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 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.
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.
My goal after clicking the register button is:
Make input fields blank
Do not show error tooltips
Here is the link on CodeSandbox
I've already tried using event.target.reset(); however the tooltips are still appearing on the screen.
export default function App() {
const [showSucessAlert, setshowSucessAlert] = useState(false);
const [validated, setValidated] = useState(false);
const [transitionAlert, setTransitionAlert] = useState(false);
const handleSubmit = (event) => {
const form = event.currentTarget;
event.preventDefault();
if (form.checkValidity() === false) {
event.stopPropagation();
} else {
handleClickTransitionAlert();
setshowSucessAlert(true);
}
setValidated(true);
};
const handleClickTransitionAlert = () => {
setTransitionAlert(true);
setTimeout(() => {
setTransitionAlert(false);
}, 1700);
};
return (
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="position-relative" controlId="validationPassword">
<Form.Label>Password</Form.Label>
<InputGroup hasValidation id="validationPassword" />
<Form.Control
type="password"
aria-describedby="validationPassword"
required
/>
<Form.Control.Feedback tooltip type="invalid">
Please enter your Password.
</Form.Control.Feedback>
</Form.Group>
<Alert
className={`mt-1 p-1 position-fixed ${
transitionAlert ? "alert-shown" : "alert-hidden"
}`}
show={showSucessAlert}
variant="success"
>
Registered user!
</Alert>
<Button className="mt-5" variant="primary" type="submit">
Register
</Button>
</Form>
);
}
Here is the link on CodeSandbox
Every help is welcome!
I don't commonly use uncontrolled components, but I think you could solve this by adding setValidated(false) and event.target.reset() to the handleClickTransitionAlert, like this:
const handleClickTransitionAlert = (event) => {
setTransitionAlert(true);
setTimeout(() => {
setTransitionAlert(false);
setValidated(false)
event.target.reset()
}, 1700);
};
Try reseting the validated attribute on Bootsrap Form.
it should look something like this (this is pseudo-code):
import React, { useRef, useState } from 'react';
const FormComponent = () => {
const [validated, setValidated] = useState(false);
const formRef = useRef(null);
const handleReset = () => {
formRef.current.reset();
setValidated(false);
};
const handleSubmit = () => {
// Do stuff here
// On success or error:
setValidated(true);
handleReset();
}
return(
<Form ref={formRef} validated={validated} onSubmit={handleSubmit}>
// your form inputs
</Form>
);
export default FormComponent;
}
I have the following Form:
const MyForm = () => {
return (
<>
<Formik
validateOnChange={true}
initialValues={{ plan: "", email: "", name: "" }}
validate={values => {
console.log(values)
const errors = {}
if (values.plan !== "123" && values.plan !== "456") {
errors.plan = "Not valid"
} else if (values.plan === "") {
errors.plan = "Please enter something"
}
if (!values.email) {
errors.email = "Please provide an e-mail address."
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Please provide a valid e-mail address."
}
return errors
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false)
}, 400)
}}
>
{({ isSubmitting, errors }) => (
<Form>
<FieldWrapper>
<InputField type="text" name="plan" label="plan" />
<StyledErrorMessage name="plan" component="div" />
</FieldWrapper>
<Button
disabled={errors.plan}
>
Continue
</Button>
</Form>
)}
</Formik>
</>
)
}
I have a Continue Button and I want it to be disabled if there are any errors. I am doing <Button disabled={errors.plan}> and this works.
However: it does not disable to Button when the user just doesn't touch the field at all - since then, the validation isn't called and consequently, there won't be any errors in the error object. So initially, the button is not disabled.
How can I circumvent this?
I'm not too familiar with Formik, but could you add a state for completed status of the form, that is initially set to false, and when completed setState(true). Then your conditional for <Button> can check both errors.plan && completedState.