Yup conditional base validation with react hook form - javascript

I tried to do the validation using yup with react hook form. When the first checkbox is enabled I want to make the second checkbook required. When the second checkbox is enabled remaining fields should be required.
yup.object().shape({
firstName: yup.string().required(),
age: yup.number().required().positive().integer(),
website: yup.string().url(),
h1: yup.boolean(),
h2: yup.boolean().test("required", "h2 required", function validator(val) {
const { h1 } = this.parent;
return h1;
})
and my codesandbox link is https://codesandbox.io/s/react-hook-form-validationschema-forked-pmcgo?file=/src/index.js:178-471
How to fix this issue

As both h1 and h2 are in the same level of the object, you can use when on the h2.
From yup docs:
let schema = object({
foo: array().of(
object({
loose: boolean(),
bar: string().when('loose', {
is: true,
otherwise: (s) => s.strict(),
}),
}),
),
});

Related

Formik handleSubmit is not getting called

I'm trying to validate a form before submitting using formik and yup validation. The form consist of two parts, the first form is validated then loads next one. And am setting a state handleShow(true) to trigger the second form. Below is my code
const UserOnboardSchema = Yup.object().shape({
gender: Yup.string().required('please select the gender'),
firstName: Yup.string().required('Please enter your first name'),
lastName: Yup.string().required('Please enter your last name'),
mobile: Yup.string()
.required('Please enter your mobile number')
.matches(phoneRegExp, 'Please enter valid phone number'),
workExperience: Yup.string().required('Please enter your work experience'),
});
const formik = useFormik({
initialValues: {
gender: '',
firstName: '',
lastName: '',
mobile: '',
workExperience: '',
currentRole: '',
},
validationSchema: UserOnboardSchema,
onSubmit: (values) => {
console.log(values);
formik.resetForm();
},
});
const handleSubmit = (e) => {
e.preventDefault();
formik.handleSubmit();
if (Object.entries(formik.errors).length === 0) {
handleShow(true);
} else {
handleShow(false);
}
};
Here is the problem in the handleSubmit the formik.handleSubmit is not working. It's directly accessing the if/else condition thus loading second form without validating the first one.
if (Object.entries(formik.errors).length === 0) {
handleShow(true);
} else {
handleShow(false);
}
but if I givehandleShow(true) direclty to formik, like this
const formik = useFormik({
initialValues: {
gender: '',
firstName: '',
lastName: '',
mobile: '',
workExperience: '',
currentRole: '',
},
validationSchema: UserOnboardSchema,
onSubmit: (values) => {
console.log(values);
handleShow(true); #----> Giving here.
formik.resetForm();
},
});
then the formik and Yup validation works. Im unable to figure out whats causing this issue?
It's difficult to see what's wrong without the form itself. Here are some helpful troubleshooting tips though...
Formik won't trigger the onSubmit method, if there are errors in the form. So, my go-to would be to:
Check for any errors in the form,
Look at the initialValues object for any unused fields

Validation to prevent brand names being in the email field?

So, I have this list of some brandnames:
const blackListedDomains = [
'facebook',
'google',
'amazon',
'netflix',
'hotmail',
'microsoft',
'gmail',
'yahoo',
]
which I need to prevent users to enter in the email field, what Schema could be built using Yup Package.
For example, I am trying this way:
const DomainSchema = Yup.object().shape({
subdomain: Yup.string()
.required('Required')
.lowercase()
.notOneOf(
blackListedDomains,
'Cannot use trademarked names in domain!',
),
})
But, I don't know how to get it from the email string, for example, if user inputs xyz#google.xyz, then how to extract google from the email string and then iterate it over the domain names list to find if it exists there and then show an error message ?
You can use test function of yup library to create a custom validation rule like this:
const DomainSchema = Yup.object().shape({
email: Yup.string()
.required("Required")
.lowercase()
.email("Not a valid mail")
.test(
"is-not-blacklist",
"Cannot use trademarked names in domain!",
(value) => {
if (value) {
const currentDomain = value.substring(
value.indexOf("#") + 1,
value.indexOf(".")
);
return !blackListedDomains.includes(currentDomain);
}
}
)
});
You can take a look at this sandbox for a live working example of this solution.

React-hook-form validation - IsValid prop keeps returning false

I have a fairly complex form using React-Hook-Form. I am unable to get the validation to work correctly.
The zod library validation schema is as such:
// For the form to be valid,
// Atleast 2 goal forms, with min. 1 task
export const schemaZod = z.object({
goals: z
.object({
title: z.string().min(3, { message: "required" }).max(40),
intro: z.string().max(250).optional(),
deadline: (z.string() || z.date()).optional(),
task: z
.object({
content: z.string().min(3, { message: "required" }).max(50),
dayFrequency: z.number().min(0).max(5).optional(),
weekFrequency: z.number().min(0).max(7).optional(),
})
.array()
.min(1),
})
.array()
.min(2),
});
const ctx = useForm({
mode: "onChange",
resolver: zodResolver(schemaZod),
});
Here is a working CodeSandBox example, sincere apologies for so much code. Its a complex form & I am unable to get the validation formState.isValid to be true, even when the conditions are met.

Setting error on form control field based on the value of another field

I have an edit user dialog box where I'm allowing the user to edit the user attributes. For editing the password, I need the user to enter the new password & confirm it. The confirm password field should be mandatory only if the password field has some value as the user can edit other values apart from the password too. I'm trying to do this through a custom validator but am unable to do so. Please help. Thanks in advance.
Here is my form group:
this.userForm = new FormGroup({
username: new FormControl(this.data.user.username, [Validators.required]),
firstName: new FormControl(this.data.user.firstName, [Validators.required]),
email: new FormControl(this.data.user.email, [Validators.required, Validators.email]),
password: new FormControl('', []),
passwordAgain: new FormControl('', [isEmpty('password') ]),
});
My custom validator:
export function isEmpty(matchingControlName: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
if (!control.parent) {
return null;
}
const dest = control.parent.controls[matchingControlName];
// // set error if validation fails
if (control.value==='' && dest.value!='') {
return { empty: true };
} else {
return null;
}
};
}
First you can add another condition in your your custom validator
if (control.value==='' && dest.value!='') {
return { empty: true };
} else {
return control.value!=dest.value?{notMatch:true}:null;
}
Yes a custom validator can return different errors -you check *ngIf="userForm.get('passwordAgain').errors?.empty" and *ngIf="userForm.get('passwordAgain').errors?.notMatch"
But the main problem is that Angular only check the validator of a control if you change the control. For this you can take two approach
1.- in submit makes an updateValueAndValidity over the control passwordAgain before send the value
submit(userForm:FormGroup){
//updateValue and validity the "passwordAgain"
userForm.get('passwordAgain').updateValueAndValidity();
//mark all controls as touched
userForm.markAllAsTouched()
if (userForm.valid)
{
.....your code...
}
}
2.-After create the form subscribe to password valueChange to updateValueAndValidity the paswwrodAgain
this.userForm.get('password').valueChanges.subscribe(_=>{
this.userForm.get('passwordAgain').updateValueAndValidity()
})
Update After see the MikOne's comment there are another option that is make the validator over the formGroup -this makes any change in any input execute the validator-

Conditional Validation in Yup

I have an email field that only gets shown if a checkbox is selected (boolean value is true). When the form get submitted, I only what this field to be required if the checkbox is checked (boolean is true).
This is what I've tried so far:
const validationSchema = yup.object().shape({
email: yup
.string()
.email()
.label('Email')
.when('showEmail', {
is: true,
then: yup.string().required('Must enter email address'),
}),
})
I've tried several other variations, but I get errors from Formik and Yup:
Uncaught (in promise) TypeError: Cannot read property 'length' of undefined
at yupToFormErrors (formik.es6.js:6198)
at formik.es6.js:5933
at <anonymous>
yupToFormErrors # formik.es6.js:6198
And I get validation errors from Yup as well. What am I doing wrong?
You probably aren't defining a validation rule for the showEmail field.
I've done a CodeSandox to test it out and as soon as I added:
showEmail: yup.boolean()
The form started validation correctly and no error was thrown.
This is the url: https://codesandbox.io/s/74z4px0k8q
And for future this was the correct validation schema:
validationSchema={yup.object().shape({
showEmail: yup.boolean(),
email: yup
.string()
.email()
.when("showEmail", {
is: true,
then: yup.string().required("Must enter email address")
})
})
}
Formik author here...
To make Yup.when work properly, you would have to add showEmail to initialValues and to your Yup schema shape.
In general, when using validationSchema, it is best practices to ensure that all of you form's fields have initial values so that Yup can see them immediately.
The result would look like:
<Formik
initialValues={{ email: '', showEmail: false }}
validationSchema={Yup.object().shape({
showEmail: Yup.boolean(),
email: Yup
.string()
.email()
.when("showEmail", {
is: true,
then: Yup.string().required("Must enter email address")
})
})
}
/>
You can even use a function for complex cases . Function case helps for complex validations
validationSchema={yup.object().shape({
showEmail: yup.boolean(),
email: yup
.string()
.email()
.when("showEmail", (showEmail, schema) => {
if(showEmail)
return schema.required("Must enter email address")
return schema
})
})
}
Totally agree with #João Cunha's answer. Just a supplement for the use case of Radio button.
When we use radio button as condition, we can check value of string instead of boolean. e.g. is: 'Phone'
const ValidationSchema = Yup.object().shape({
// This is the radio button.
preferredContact: Yup.string()
.required('Preferred contact is required.'),
// This is the input field.
contactPhone: Yup.string()
.when('preferredContact', {
is: 'Phone',
then: Yup.string()
.required('Phone number is required.'),
}),
// This is another input field.
contactEmail: Yup.string()
.when('preferredContact', {
is: 'Email',
then: Yup.string()
.email('Please use a valid email address.')
.required('Email address is required.'),
}),
});
This the radio button written in ReactJS, onChange method is the key to trigger the condition checking.
<label>
<input
name="preferredContact" type="radio" value="Email"
checked={this.state.preferredContact == 'Email'}
onChange={() => this.handleRadioButtonChange('Email', setFieldValue)}
/>
Email
</label>
<label>
<input
name="preferredContact" type="radio" value="Phone"
checked={this.state.preferredContact == 'Phone'}
onChange={() => this.handleRadioButtonChange('Phone', setFieldValue)}
/>
Phone
</label>
And here's the callback function when radio button get changed. if we are using Formik, setFieldValue is the way to go.
handleRadioButtonChange(value, setFieldValue) {
this.setState({'preferredContact': value});
setFieldValue('preferredContact', value);
}
email: Yup.string()
.when(['showEmail', 'anotherField'], {
is: (showEmail, anotherField) => {
return (showEmail && anotherField);
},
then: Yup.string().required('Must enter email address')
}),
it works for me very well :
Yup.object().shape({
voyageStartDate:Yup.date(),
voyageEndDate:Yup.date()
.when(
'voyageStartDate',
(voyageStartDate, schema) => (moment(voyageStartDate).isValid() ? schema.min(voyageStartDate) : schema),
),
})
I use yup with vee-validate
vee-validate
here is the sample code from project
const schema = yup.object({
first_name: yup.string().required().max(45).label('Name'),
last_name: yup.string().required().max(45).label('Last name'),
email: yup.string().email().required().max(255).label('Email'),
self_user: yup.boolean(),
company_id: yup.number()
.when('self_user', {
is: false,
then: yup.number().required()
})
})
const { validate, resetForm } = useForm({
validationSchema: schema,
initialValues: {
self_user: true
}
})
const {
value: self_user
} = useField('self_user')
const handleSelfUserChange = () => {
self_user.value = !self_user.value
}

Categories