Conditional validation with Yup and Formik - javascript

Here is my validation schema:
const validationSchema = Yup.object().shape({
person: Yup.object().shape({
name: Yup.string().required('Field is required'),
surname: Yup.string().required('Field is required'),
middleName: Yup.string().required('Field is required'),
email: Yup.string()
.email('Wrong e-mail format')
.required('Field is required')
}),
company: Yup.object().shape({
name: Yup.string().required('Field is required'),
address: Yup.string().required('Field is required'),
email: Yup.string()
.email('Wrong e-mail format')
.required('Field is required')
})
});
And also there are two variables in React State: isPerson and isCompany. How to make validation work conditionally, for example if isPerson is true, then person in validationSchema is required to be validated?

Updated ans: 2020.
you can use Yup conditions
const validationSchema = Yup.object().shape({
isCompany: Yup.boolean(),
companyName: Yup.string().when('isCompany', {
is: true,
then: Yup.string().required('Field is required')
}),
companyAddress: Yup.string().when('isCompany', {
is: (isCompany) => true,//just an e.g. you can return a function
then: Yup.string().required('Field is required'),
otherwise: Yup.string()
}),
});
And make sure to update your form accordingly. I hope you get the point...

You can conditionally add to your validation schema just like any other object:
let validationShape = {
company: Yup.object().shape({
name: Yup.string().required('Field is required'),
address: Yup.string().required('Field is required'),
email: Yup.string()
.email('Wrong e-mail format')
.required('Field is required')
})
};
if (this.state.isPerson) {
validationShape.person = Yup.object().shape({
name: Yup.string().required('Field is required'),
surname: Yup.string().required('Field is required'),
middleName: Yup.string().required('Field is required'),
email: Yup.string()
.email('Wrong e-mail format')
.required('Field is required');
}
const validationSchema = Yup.object().shape(validationShape);

Conditional validations with YUP :
All kinds of validation that can be done with when are as follows:
1. Single value, simple condition :
RULE: Only ask for personName when isPerson true.
personName : Yup.string().when('isPerson', {
is: true,
then: Yup.string().required('Field is required'),
otherwise: Yup.string(),
})
2. Single value, complex condition :
RULE: Only ask for personName when company is "IT".
personName : Yup.string().when('company', {
is: (company)=> company==='IT',
then: Yup.string().required('Field is required'),
otherwise: Yup.string(),
})
3. Multi value, complex condition :
RULE: Only ask for personName when company is "IT" and person is valid.
personName : Yup.string().when(['company', 'isPerson'], {
is: (company,isPerson)=> company==='IT'&& isPerson,
then: Yup.string().required('Field is required'),
otherwise: Yup.string(),
})

While the accepted solution works, it had one problem - two of the fields to be validated were common, and had to be duplicated. In my case, I had majority of the fields common with just 2-4 outliers.
So here is another solution:
Define each schema separately - i.e. 3 schemas - commonSchema for the common fields, personSchema for person specfic fields & companySchema for company specific fields.
Merge the schemas based on the state
const validationSchema = isPerson
? commonSchema.concat(personSchema)
: commonSchema.concat(companySchema)
For details on "concat", refer to the yup docs on github.

email: Yup.string()
.when(['showEmail', 'anotherField'], {
is: (showEmail, anotherField) => {
return (showEmail && anotherField);
},
then: Yup.string().required('Must enter email address')
}),
Multiple fields can also be used for validation.

Related

How to add custom string value in validation schema using Yup

I have this basic yup validation schema for my react project.
const DisplayingErrorMessagesSchema = Yup.object().shape({
username: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
email: Yup.string().email("Invalid email").required("Required")
});
For username, it currently validates the required property when the username field is an empty string, but to validate even more with multiple default strings like "none", and "admin". How can I validate these default strings also for the username field?
They have matches() method that does what you are asking:
username: Yup.string()
.matches(/(none|default)/, { message: "Username invalid", excludeEmptyString: true })
.required("Required"),

yup conditional validation of array

I want to validate the array on the base of loan register if loan register is true then array should validate else not.
yup.object().shape({
loan_register: yup.boolean(),
loans: yup.array()
.of(
yup.object().shape({
bank_name: yup.string().required(),
bank_reg_no: yup.string().required(),
loan_amount: yup.string().required(),
})
)
})
The reason why of is not an exported member from yup is because yup needs to know the type of data first. You can only use of once you know the type.
For example: array().of(), string().oneOf(), e.t.c
Hence, in your case, you need to provide the data type and it will solve your problem.
const validationSchema = yup.object({
loan_register: yup.boolean(),
loans: yup.array().when('loan_register', {
is: true,
then: yup.array().of(
yup.object({
bank_name: yup.string().required(),
bank_reg_no:yup.string().required(),
loan_amount:yup.string().required(),
}))
})
})
EDIT: 'When loan_register === true, bank_name, bank_reg_no and loan_amount must be strings and required fields.'
You can translate that requirement into code like following (include Yup conditional validation using .when() ):
const validationSchema = yup.object().shape({
loan_register: yup.boolean(),
loans: yup.array()
.when('loan_register', {
is: true,
then: yup.of(
yup.object().shape({
bank_name: yup.string().required(),
bank_reg_no: yup.string().required(),
loan_amount: yup.string().required(),
})
)
})
})
I think you'll want to utilize .when(). This allows exactly what you're looking for by providing conditional validation checks based off other attribute values.
It has a more explicit approach where you'd add
.when('loan_register', {is: true, then: /* yup schema */ })
I believe the change would look like
yup.object().shape({
loan_register: yup.boolean(),
loans: yup.array()
.when('loan_register', {
is: true,
then: yup.of(
yup.object().shape({
bank_name: yup.string().required(),
bank_reg_no: yup.string().required(),
loan_amount: yup.string().required(),
})
)
})
})

Yup: Ensure at least one of the elements in an array is valid

I have the following schema:
const baseSchema = {
name: Yup.array().of(
Yup.object().shape({
lang: Yup.string().required(),
value: Yup.string()
.min(2)
.max(20)
.required()
})
)
};
Is there a way to consider the schema valid if at least one of the objects in name is valid?
Edit: essentially it has to use when(), but what I'm struggling with is to find out how to check the other elements in the array.
You can use xor if you want one of them to be required:
const baseSchema = {
name: Yup.array().of(
Yup.object().shape({
lang: Yup.string().required().allow(""),
value: Yup.string()
.min(2)
.max(20)
.required().allow("")
})
)
};

Yup cyclic dependency: Two fields mutually requiring each other

I have the following Yup validation schema
const validationSchema = Yup.object().shape({
name: Yup.string(),
services: Yup.array(Yup.string().oneOf(SERVICES, "Invalid service!")),
locations: Yup.array(Yup.string().oneOf(LOCATIONS, "Invalid location!")),
distance: Yup.number()
.typeError("Invalid distance!")
.positive("Invalid distance!")
.when("userFormattedAddress", {
is: (val) => !!val,
then: Yup.number().required(),
otherwise: Yup.number(),
}),
userFormattedAddress: Yup.string("Invalid user location!").when("distance", {
is: (val) => !!val,
then: Yup.string().required(),
otherwise: Yup.string(),
}),
userCoordinates: Yup.array(
Yup.number("Invalid user location!").positive("Invalid user location!")
),
});
The desired behaviour is that when a distance is entered, the user must enter an address, and when a user enters an address , they must specify a distance too. However, I run into a cyclic dependency... Any thoughts? Thanks!
Okay, I found the answer:
const validationSchema = Yup.object().shape({
name: Yup.string(),
services: Yup.array(Yup.string().oneOf(SERVICES, "Invalid service!")),
locations: Yup.array(Yup.string().oneOf(LOCATIONS, "Invalid location!")),
distance: Yup.number()
.typeError("Invalid distance!")
.positive("Invalid distance!")
.when("userFormattedAddress", {
is: (val) => !!val,
then: Yup.number().required(),
otherwise: Yup.number(),
}),
userFormattedAddress: Yup.string("Invalid user location!").when("distance", {
is: (val) => !!val,
then: Yup.string().required(),
otherwise: Yup.string(),
}),
userCoordinates: Yup.array(
Yup.number("Invalid user location!").positive("Invalid user location!")
),
}, ["distance", "userFormattedAddress"]);
where you need to pass the fields in an array as the noSortedEdges argument

Convert a function that returns an object to a map of that object

Requirements
I have this getValidationSchema` function. My task entails converting the function that calls it to a map of an object.
Something similar to validationSchema[EDIT_INFO] = someSchema, and call that with all my mappings instead of a function.
export const validationSchemas = {
ADD_USER: Yup.object().shape({
username: Yup.string('Provide a username').required('Username is required'),
email: Yup.string().email('Provide a valid email address'),
password: Yup.string('Provide a password').required('Password is required'),
confirmPassword: Yup.string('Provide your password again')
.required('Password confirmation is required')
.oneOf([Yup.ref('password')], 'Passwords do not match'),
group: Yup.string('Please select a group').required('Group is required')
}),
EDIT_INFO: Yup.object().shape({
username: Yup.string('Provide a username').required('Username is required'),
email: Yup.string().email('Provide a valid email address'),
group: Yup.string('Please select a group').required('Group is required')
}),
EDIT_PASSWORD: Yup.object().shape({
password: Yup.string('Provide a password').required('Password is required'),
confirmPassword: Yup.string('Provide your password again')
.required('Password confirmation is required')
.oneOf([Yup.ref('password')], 'Passwords do not match')
})
};
Here is the function I had until now:
const { mode } = this.props;
if (mode === ActionMode.EDIT_INFO) {
return validationSchemas.EDIT_INFO;
}
if (mode === ActionMode.EDIT_PASSWORD) {
return validationSchemas.EDIT_PASSWORD;
}
return validationSchemas.ADD_USER;
};
This is how i call the function until now.
validationSchema={this.getValidationSchemas}
And this is what I have done until now:
const mapValidationSchema: any;
validationSchemas[(validationSchemas.EDIT_PASSWORD, validationSchemas.EDIT_INFO,
validationSchemas.ADD_USER)] = this.mapValidationSchema;
validationSchema = {this.mapValidationSchema}
I clearly am doing something wrong, as I get multiple errors and the form validation isn't working. Note that the form is in React, and TS.
Also, I was asked to do this, but can you explain to me why is this better than a function with an if statement instead? Except looking cleaner I guess I don't see any merit to this.

Categories

Resources