Yup and react hook form input validation bug - javascript

I have a problem in the input validation. The validation works when I submit and a error message appears, but when I press the first key on the keyboard nothing appears in the textarea and the error message disappears; after that, I can write normally. Its an inconvenience and I don't know why its happening. I am using the TextArea from Material UI. The code snippet of a login form is below.
const schema = yup.object().shape({
username: yup.string().matches(/^[a-z0-9]+$/, 'Must be all lower-case letters.').required(),
password: yup.string().required(),
})
const Login = props => {
const [formValues, setFormValues] = React.useState({
username: "",
password: ""
});
const { register,errors, handleSubmit } = useForm({
resolver: yupResolver(schema),
mode: 'onSubmit',
});
const onSubmit = async (data, e) => {
e.preventDefault()
const isValid = await schema.isValid(data)
if(isValid){
console.log(data);
}
}
return (
<Container component="main" maxWidth="xs">
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
autoFocus
required
fullWidth
id="username"
label="Username"
name="username"
value={formValues.username}
inputRef={register}
helperText = {errors.username?.message}
/>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={formValues.password}
inputRef={register}
helperText = {errors.password?.message}
/>
<Button
type="submit"
fullWidth
className={classes.submit}
>
Login
</Button>
</form>
</div>
</Container>
);
}

I worked around this (in React Native) by using reValidateMode:"onBlur" in the useForm() options.
This way it doesn't retry the validation until the user leaves the input.

Related

Show validation feedback to the user

I am making a simple react form with a react-hook-form and yup as the validator. If the user's input is successfully validated, I want to show some feedback to the user, like a green outline. The problem is that I only want to show the green outline when my input has been validated, not every time the component is rendered. How can I do this? Component:
const schema = yup.object().shape({
email: yup
.string()
.required("This field is required"),
password: yup.string().required("This field is required"),
});
export const Form = () => {
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm({
mode: "onBlur",
resolver: yupResolver(schema),
});
return (
<form noValidate onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input
id="email"
{...register("email")}
/>
{errors.email ? errors?.email.message : null}
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
{...register("password")}
/>
{errors.password ? errors?.password.message : null}
<button
type="submit"
>
Submit
</button>
</form>
);
};
You can implement what you want with the touched property. Using the touched property for this kind of scenario is very common. Here's what you can do:
From the useForm hook, you can also extract the touchedFields
const {
register,
handleSubmit,
getValues,
formState: { errors, touchedFields }
} = useForm({
mode: "onBlur",
resolver: yupResolver(schema)
});
The touchedFields properties stores the touched fields. A field is touched the first time the user leaves the focus from that field. So, then you can conditionally show a message about a field if it is touched and it has no errors like this:
{touchedFields.email && !errors.email ? <div>Email ok</div> : null}
You can try this sandbox. I hope you can get the idea from it.

An Elegant Way to add Confrim Password field in React

I have a project in which I have to add a registration form and I want to to validate that the password and confirm fields are equal without clicking the register button.
If password and confirm password field will not match, then I also want to put an error message at side of confirm password field and disable registration button.
I had these for handle password and username
const LoginForm = ({ register = false }) => {
const [isLoading, setLoading] = React.useState(false)
const [errors, setErrors] = React.useState([])
const [username, setUsername] = React.useState('')
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const handleUsernameChange = React.useCallback(
(e) => setUsername(e.target.value),
[setUsername]
)
const handleEmailChange = React.useCallback(
(e) => setEmail(e.target.value),
[]
)
const handlePasswordChange = React.useCallback(
(e) => setPassword(e.target.value),
[]
)
and Got handle submit
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
try {
let data, status
if (register) {
;({ data, status } = await UserAPI.register(username, email, password))
} else {
;({ data, status } = await UserAPI.login(email, password))
}
if (status !== 200 && data?.errors) {
setErrors(data.errors)
}
if (data?.user) {
// We fetch from /profiles/:username again because the return from /users/login above
// does not contain the image placeholder.
const { data: profileData, status: profileStatus } = await UserAPI.get(
data.user.username
)
if (profileStatus !== 200) {
setErrors(profileData.errors)
}
data.user.effectiveImage = profileData.profile.image
window.localStorage.setItem('user', JSON.stringify(data.user))
setCookie('auth', data.user.token)
mutate('user', data.user)
Router.push('/')
}
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
I want to add new confirm password field to this
<form onSubmit={handleSubmit}>
<fieldset>
{register && (
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Username"
value={username}
onChange={handleUsernameChange}
/>
</fieldset>
)}
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="email"
placeholder="Email"
value={email}
onChange={handleEmailChange}
/>
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</fieldset>
<button
className="btn btn-lg btn-primary pull-xs-right"
type="submit"
disabled={isLoading}
>
{`${register ? 'Sign up' : 'Sign in'}`}
</button>
</fieldset>
</form>
What is the most elegant way to add confirm password validation?
The elegant way to create a form, in general, is using a form library. The form libraries will make your work easier, more elegant, and more developable. Most of them have a technique to use a function or a scheme as a validator that will help you certify your password.
The most popular form libraries currently are Formik and React Hook Form and if you are using Redux you can use Redux Form.
In case you want to continue your current way of handling the form the best possible name for the second field is passowrdConfirmation is the best name in my Idea. Furthermore, you can create a validation function that you process before every field change(using a useEffect hook) or before submitting(using onSubmit event on form element).
You can use "useEffect" hook to listen to password and confirm password inputs.
Here is a simple example: https://codesandbox.io/s/solitary-brook-tw15c

How to empty a list to clear input field after form submission?

I'm trying to develop a form with React-Bootstrap and using Form.Control.Feedback to show the errors.
What I want to do now is after the user submitted the form successfully, both the input fields will be cleared so that if the user wants to submit again, a page reload/refresh won't be needed.
However, I can't seem to make it work by using setForm({}) as I will get the error length.undefined after submitting again.
You will need to have a few changes to your code:
First, Only check for errors if your form is not empty:
const newErrors = form && findFormErrors(); // <-- Check for form is not empty first
Second, make your input a controlled component by adding values from state to it:
<Form.Control
type="text"
placeholder="Enter Name"
value={form.name} // <-- HERE
onChange={(e) => setField("name", e.target.value)}
isInvalid={!!errors.name}
required
/>;
Lastly, initialize your state with empty values, so the error about un-controlled component to controlled component won't appear.
const [form, setForm] = useState({ name: "", password: "" });
Working example:
const onSubmit = (e) => {
e.preventDefault();
const newErrors = findFormErrors();
// Conditional logic:
if (Object.keys(newErrors).length > 0) {
// We got errors!
setErrors(newErrors);
} else {
// No errors! Put any logic here for the form submission!
alert("Thank you for your feedback! " + form.name + " " + form.password);
setForm({ name: "", password: "" }); **// This was added**
console.log(form);
}
};
........................................
<Col sm="6">
<Form.Control
type="text"
value={form.name} **// This was added**
placeholder="Enter Name"
onChange={(e) => setField("name", e.target.value)}
isInvalid={!!errors.name}
required
/>
<Form.Control.Feedback type="invalid">
{errors.name}
</Form.Control.Feedback>
</Col>
</Form.Group>
<Form.Group as={Row} className="mb-3">
<Form.Label className="fw-bold" column sm="4">
Password
</Form.Label>
<Col sm="6">
<Form.Control
type="password"
value={form.password} **// This was added**
placeholder="New Password"
onChange={(e) => setField("password", e.target.value)}
isInvalid={!!errors.password}
required
/>
<Form.Control.Feedback type="invalid">
{errors.password}
</Form.Control.Feedback>
</Col>
</Form.Group>
..................................
There were a few changes required.
Setting setForm({ name: "", password: "" });.
Setting value to the input fields.

Yup and useForm validation errors does not trigger when contact is form is empty

I am using react-hooks and yup for my contact form. I have added my yup schema as expected and error boundaries but they don't trigger when I try to submit it with empty fields. The empty form goes straight to the database which I don't want. Why is it not triggering?
My contact form code is as follows:
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "#hookform/resolvers/yup";
import { ErrorMessage } from "#hookform/error-message";
const schema = yup.object().shape({
fullName: yup.string().required("Please enter your full name"),
email: yup.string().required("Please enter your email"),
select: yup
.string()
.oneOf(["webSolutions", "mobileSolutions", "devOps", "research", "uiux"])
.required("Please choose one of our services"),
message: yup.string().required("Message can not be left blank"),
});
const Contact = () => {
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [selecttype , setSelectType ] = useState([]);
const {
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const handleSubmit = (e) => {
e.preventDefault();
db.collection("contacts")
.add({
fullName: fullName,
email: email,
selecttype : selecttype ,
message: message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
setFullName("");
setEmail("");
setMessage("");
setSelectType("");
};
return (
<>
<Container>
<FormBg>
<ImageBg>
<Img src={Image} />
</ImageBg>
</FormBg>
<FormWrap>
<FormContent>
<Form onSubmit={handleSubmit}>
<Icon to="/">
<img src={dLogo} />
</Icon>
<FormH1>Fill in your request details below</FormH1>
<FormLabel value="fullName">
Full Name <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="text"
name="fullName"
onChange={(e) => setFullName(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="fullName" />
</ErrorTag>
<FormLabel value="email">
Email <RequiredTag>*</RequiredTag>
</FormLabel>
<FormInput
type="email"
name="email"
placeholder="example#email.com"
onChange={(e) => setEmail(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="email" />
</ErrorTag>
<FormLabel value="services">
What would you wants to do for you?
<RequiredTag>*</RequiredTag>
</FormLabel>
<select
onChange={(e) => {
const selectedOption = e.target.value;
setSelectType (selectedOption);
console.log(selectedOption);
}}
>
<option>Select ...</option>
<option value="webSolutions">Web Solutions</option>
<option value="mobileSolutions">Mobile Solutions</option>
<option value="devOps">DevOps Solutions</option>
<option value="research">Research Assistance</option>
<option value="uiux">UI/UX Design</option>
</select>
<ErrorTag>
<ErrorMessage errors={errors} name="select" />
</ErrorTag>
<FormLabel value="message">
Message <RequiredTag>*</RequiredTag>
</FormLabel>
<textarea
name="message"
placeholder="Tell us more about your request like bugdets, questions and more"
onChange={(e) => setMessage(e.target.value)}
/>
<ErrorTag>
<ErrorMessage errors={errors} name="message" />
</ErrorTag>
<FormButton type="submit">Submit Request</FormButton>
<Text>We respond within 48 hours😉</Text>
</Form>
</FormContent>
</FormWrap>
</Container>
</>
);
};
export default Contact;```
You can also set .required() on your yup.object().shape({...}) which will make sure that the object will not be empty when validating.
It will look like this:
const schema = yup.object().shape({
// Your shape fields
}).required(); // -> here is where you add it
Edit
You are also not using React Hook Form's submit wrapper, this is probably why the form gets submit even though there are errors.
const { handleSubmit } = useForm();
const yourSubmitHandler = () => {
// ...
};
return (
<Form onSubmit={handleSubmit(yourSubmitHandler)}>
{/* Rest of the form */}
</Form>
);
I found the answer. Since I was using react-hooks useForm and yup to validate my form, I had to use the data captured in the form by passing it to the onSubmit function to my firebase db like this:
const onSubmit = (data) => {
db.collection("contacts")
.add({
fullName: data.fullName,
email: data.email,
select: data.select,
select2: data.select2,
message: data.message,
})
.then(() => {
alert("message has been submitted");
})
.catch((error) => {
alert(error.message);
});
};
This works perfectly and I hope it does for anyone else who might be using react-hook useForm and yup for validation.

I'm not Getting any number of calls in my React Test using React Testing Library

I mocked my login component using react testing library, did the necessary "getBy's" and used fireEvent to submit or click on the submit button. I rendered the component with an onSubmit function but it still doesn't get called.
it("Calls onSubmit with username and Password when submitted", () => {
const onSubmit = jest.fn();
const { getByTestId, getByText } = render(
<MemoryRouter>
<Login onSubmit = {onSubmit} />
</MemoryRouter>
);
const button = getByText("Login");
const email = getByTestId("email");
const password = getByTestId("password");
const form = getByTestId("form-element");
fireEvent.change(email, { target: { value: "Tolulope#gmail.com" } });
fireEvent.change(password, { target: { value: "YummyPizza" } });
fireEvent.click(button);
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
email: email.value,
password: password.value,
});
});
I tried but fireEvent.submit and fireEvent.click on the form and button respectively but both seem not to trigger the function.
Here is the setup for my login component.
<form data-testid="form-element" style={{width: "360px"}}>
<Input data-testid= "email" onChange={handleChange} value={email} name='email' type='text' label='Email Address' icon = 'at' required/>
<Input data-testid= "password" onChange={handleChange} value={password} name='password' type='password' label='Password' icon = 'lock' required/>
<ButtonAuth className='text-xl text-white uppercase cursor-pointer mt-1 block w-full h-12 outline-none border-none bg-green-500 mt-3' value='Login' type="submit" onSubmit={onSubmit} disabled={!enabled} />
</form>
The Input and Button are both components but reusable input and button tags.
You have to fire the submit event on a form.
it("Calls onSubmit with username and Password when submitted", () => {
const onSubmit = jest.fn();
const { getByTestId } = render(
<MemoryRouter>
<Login onSubmit={onSubmit} />
</MemoryRouter>
);
const form = getByTestId("form-element");
expect(onSubmit).not.toHaveBeenCalled();
fireEvent.submit(form, {
target: {
values: {
email: "Tolulope#gmail.com",
password: "YummyPizza",
},
},
});
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith({
email: "Tolulope#gmail.com",
password: "YummyPizza",
});
});

Categories