Access to required fields with Yup - javascript

I use react-hook-form with yup to validate my forms.
I want to know all required fields of a schema to display some information in form (like '*' for required fields).
We could achieve this with this line of code :
schema.describe().fields[field].tests.findIndex(({ name }) => name === 'required'
However, this code doesn't work for conditional validation.
Schema example :
const schema = yup.object().shape({
email: yup
.string()
.email()
.required(),
isProfileRequired: yup
.boolean(),
profile: yup
.object()
.when('isProfileRequired',{
is: (isProfileRequired) => isProfileRequired,
then:
yup
.object()
.nullable()
.required()
})
})
Is there a way to retrieve this informations within the form ?

There is actually no "nice" way to do it but this works:
function isRequired(field){
return schema.fields[field]._exclusive.required || false
}
Notice: schema.fields[field]._exclusive.required returns true if required or undefined.

Testing exclusiveTests instead of _exclusive worked for me.
const isRequired =
validationSchema?.fields[aField.name]?.exclusiveTests?.required || false;

Related

Why is my yup string array validation schema not working?

Does anyone have any idea why this isnt working? I have a schema im trying to enforce with yup and ensure that the data inside the array are strings, here is the yup schema:
signUpSchema: async (req, res, next) => {
const signUpSchema = yup.object().shape({
username: yup
.string()
.required('name is required')
.typeError('name must be a string'),
email: yup
.string()
.email('email must be a valid email')
.required('email is required')
.typeError('email must be a string'),
password: yup
.string()
.required('password is required')
.typeError('password must be a string'),
password2: yup
.string()
.oneOf([yup.ref('password'), null], 'Passwords must match'),
isArtist: yup.boolean().required('isArtist is required').typeError('isArtist must be a boolean'),
areaCode: yup.string().required('areaCode is required').typeError('areaCode must be a string'),
// ! WHY IS THIS NOT WORKING, VALIDATION OF DATA INSIDE ARRAY ISNT WORKING?
locationTracking: yup
.array()
.of(yup.string())
.required('locationTracking is required')
.typeError('locationTracking must be an array')
.min(1, 'locationTracking must have at least one location'),
});
try {
await signUpSchema.validate(req.body);
next();
} catch (error) {
next(error);
}
},
locationTracking: yup
.array()
.of(yup.string())
.required('locationTracking is required')
.typeError('locationTracking must be an array')
.min(1, 'locationTracking must have at least one location'),
});
I also attempted adding the required and typeError methods
locationTracking: yup
.array()
.of(yup.string().require().typeError('data must be strings'))
.required('locationTracking is required')
.typeError('locationTracking must be an array')
.min(1, 'locationTracking must have at least one location'),
});
the schema enforces that locationTracking is an array but does not enforce that it needs to be an array of strings. Arrays of numbers, booleans etc all fly by this validation. Not sure what im doing wrong can't find anything online about this issue.
the data being validated is the req.body being sent as json by postman, i thought theres was some sort of type coercion occuring but when i check for the type of data it returns number, boolean etc, so completely flying by my validation
let schema = yup.array().of(yup.number().min(2));
await schema.isValid([2, 3]); // => true
await schema.isValid([1, -24]); // => false
schema.cast(['2', '3']); // => [2, 3]
Refer.
Your code
locationTracking: yup
.array()
.of(yup.string().min(1, 'locationTracking must have at least one location').required())
.required('locationTracking is required')
});

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 Yup schema

I'm using Yup to validate an email field:
const Schema = Yup.object().shape({
email: Yup.string()
.email("non valid email format")
.required("Email required"),
...
During form submit, I check if the email domain is included in a list of forbidden domains, if yes, I display an error message in a popup:
const forbidDomains = domains;
const domain = data.email.split("#");
if(forbidDomains.Domains.includes(domain[1])) {
this.setState({openPopup:true,statusMessage:"domain not allowed : " + domain[1]})
this.setState({isSubmitting:false})
return;
}
I would like to check the domain in the Yup schema, but I'm not sure if it's possible.
I think that what you are looking for is the .test() from Yup. Maybe something like this may work:
const schema = {
email: Yup.string()
.string()
.email()
.test('test-name', 'Validation failure message',
function(value) {
// your logic to check the domain
})
}

Password confirmation and password are equal but validation is still triggering JOI in React component

I creating a register form and the problems accurs while try validating password confirmation. I am using the last version of JOI-Browser.
I tried the code below and the validation error was triggered even though password and password confirmation have the same values.
password: Joi.string()
.min(5)
.required(),
passwordConfirmation: Joi.ref("password")
Here is my state object:
password: "12345"
passwordConfirmation: "12345"
username: ""
errors: {…}
passwordConfirmation: "\"passwordConfirmation\" must be one of [ref:password]"
I passed several hours trying several approaches and reading the documentation, but still no luck, the validation is still triggering,
I have other validations in this form and they work fine.
I don't think Joi.ref should be used that way.
I usually tend to do this way:
const passwordConfirmation = Joi.string()
.required()
.valid(Joi.ref('password'))
.options({
language: {
any: {
allowOnly: '!!Passwords do not match',
}
}
})
If you refer to the docs, you will see:
Note that references can only be used where explicitly supported such as in valid() or invalid() rules. If upwards (parents) references are needed, use object.assert().
If anyone has encountered a similar problem, this is the solution I have used:
validateProperty = (input) => {
let obj = { [input.name]: input.value };
let schema = { [input.name]: this.schema[input.name] };
if (input.name.endsWith("_confirm")) {
const dependentInput = input.name.substring(
0,
input.name.indexOf("_confirm")
);
obj[dependentInput] = this.state.data[dependentInput];
schema[dependentInput] = this.schema[dependentInput];
}
const { error } = Joi.validate(obj, schema);
return error ? error.details[0].message : null;
};
In my case, I have looked for _confirm because I have the field names as password and password_confirm. You need to make changes here as per your requirements.
Main logic, you just need to add value and schema of password when you are validating password_confirm
I find out what was happening. My code above was right, the problem was in my validate function.
Gabriele's Petrioli comment help me out. this is the function that cause me problems:
validateProperty = ({ name: propertyName, value }) => {
const obj = { [propertyName]: value };
const schema = { [propertyName]: this.schema[propertyName] };
const { error } = Joi.validate(obj, schema);
return error ? error.details[0].message : null;};
Has you can see i tried validate each property individually, so i can make the form more dynamic.
This trigger the validation error because when i try to validate confirmPassword there was no value in password because i passed only the value the correspond to confirmaPassword, it also needed the password value to make the comparison.
Rookie mistake

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