I am trying to create a yup schema where based on a variables value the schema changes slightly. In my case depending on the value of prop myCondition I need to make a field as required. I would like to know if there is any better way I can achieve the same using Yup. Here's my current code structure that works:
// config.js
const COMMON_SCHEMA = {
str1: yup
.string()
.nullable()
.required('Please input str1'),
str3: yup
.string()
.nullable()
};
const VALIDATION_SCHEMA_1 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string().nullable(),
});
const VALIDATION_SCHEMA_2 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup
.string()
.nullable()
.required('Please input str2'),
});
const SCHEMAS = {
VALIDATION_SCHEMA_1,
VALIDATION_SCHEMA_2
}
export default SCHEMAS;
the following is how I conditionally choose different schemas:
// app.js
import SCHEMAS from './config';
...
<Formik
validationSchema={
this.props.myCondition === true
? SCHEMAS.VALIDATION_SCHEMA_1
: SCHEMAS.VALIDATION_SCHEMA_2
}
>
...
</Formik>
I feel like I can achieve whatever I am doing above in an easier way with yup. One of the approaches I tried was to pass in the prop myCondition into config file and work with it's value by using yup.when() but when only works if the value you want to use is also a part of the form so I couldn't achieve the same.
This can be achieved by the following way:
You can wrap your schema in a function and pass your conditonal prop:
const wrapperSchema = (condition) =>
Yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string()
.when([], {
is: () => condition === true,
then: yup.string().nullable(),
otherwise: yup.string().nullable().required('Please input str2'),
}),
});
Please note that first param of when is a required param, you can also pass e.g. ["title", "name"] where title and name can be your field values, and you can retrieve and use them in is callback, in the same order, if you have to.
You can conditionally add validation directly in the validationSchema, like this: https://stackoverflow.com/a/56216753/8826314
Edited to add, you can include your value in the initial form, so that you can do the when check against that field. For example:
<Formik
initialValues={
...,
str2
}
validationSchema={
...,
str2: yup.object().when('str2', {
is: str2 => condition check that returns boolean,
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().required('Please input str2')
})
}
>
...
</Formik>
Related
I want to validate my object with the schema using yup.
But I notice that when I wrote my own test function it's trigger anyway.
I mean I validate the age property for number, null, positive, integer value. then I want to continue with my own logic test.
So I expect to NOT enter the function unless the previous tests are valid.
I'm not sure if this is how it meant to be, but in this way I must also check for valid input in my tests function, even though I add the number, null, positive, integer checks.
So am I using the yup wrong?
What I expect form yup is not invoke the test if the previous tests are invalid.
stackblitz
import { object, string, number, date, InferType } from 'yup';
let userSchema = object({
age: number()
.nullable()
.positive()
.integer()
.test({
message: 'test message',
test: (v) => {
console.log('in test!', v);
return !!v.toPrecision();
},
}),
});
userSchema
.validate({ age: null })
.then((res) => {
console.log({ res });
})
.catch((e) => {
console.log({ e });
});
I have 3 fields in a form, the "type" field is select and from that list, some items enable or disable the "out" field, if is enable I need the "out" field to be less than the "in" field and vice-versa, but if the "out" field is disabled I don't need that validation, I was trying something with .when, but is not working, any ideas on how to do this?
const [disableOutCounterField, setDisableOutCounterField] = useState(false);
const schema = yup.object().shape({
type: yup.string().required(requiredMessage),
in: yup
.number(numberMessage)
.required(requiredMessage)
.integer(integerMessage)
.min(1, positiveMessage)
.typeError(numberMessage)
.when("out", {
is: !disableOutCounterField,
then: yup.number().moreThan(yup.ref("out"), moreThanMessage),
message: moreThanMessage,
}),
out: yup
.number(numberMessage)
.integer(integerMessage)
.typeError(numberMessage)
.lessThan(yup.ref("in"), lessThanMessage),
});
The construct:
.when("out", {
is: !disableOutCounterField,
compares the out value with !disableOutCounterField, and if they are equal, the then rule is applied. But quite likely they are never the same.
The check that is needed here is just the value of !disableOutCounterField by itself, for any value of out. This can be done using an expression:
.when("out", {
is: value => !disableOutCounterField,
In words: for every out value, return !disableOutCounterField, and if it returns true, apply the then part.
An alternative I found for this is to regenerate the object validationSchema from inside a useEffect.
This has the benefit of using useState without having them mapped to specific form fields ahead of time, such as if you set something conditionally from the database and don't want to set it as a hidden field somewhere.
const validationPiece = yup.object({
amount: yup
.number()
.typeError('Please enter an amount')
.required('Please enter an amount')
});
const [validationSchema, setValidaitonSchema] = useState(yup.object({}));
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (needNewPartOfForm) {
const validationSchemaDict = {};
if (needNewPartOfForm.xyz) {
validationSchemaDict['xyz'] = validationPiece; // does this need a cloneDeep?
}
if (needNewPartOfForm.abc) {
validationSchemaDict['abc'] = validationPiece;
}
setValidaitonSchema(yup.object(validationSchemaDict));
}
}, [clientDocs, clientId, reset, teamId]);
below my code.
For some reason, the iscountrycode is undefined when I put it into the .when.
in the console.log the result is 'BE'
export function getCheckoutFormSchema(t?: any, iscountryCode?: string) {`
console.log(iscountryCode)`
let schema = Yup.object().shape({`
zip: Yup.string()
.when(iscountryCode, {
is: 'BE',
then: Yup.string()`
Does anybody know why this is happening?
I think you forgot to apply quotes ('') around iscountryCode field.
Try this -
let schema = Yup.object().shape({
zip: Yup.string().when('iscountryCode', { is: 'BE', then: Yup.string()...}
)
also one more point you need to fact check -
your schema must have this field you are using 'iscountryCode',
let schema = Yup.object().shape({
iscountryCode: Yup.string(),
zip: Yup.string().when('iscountryCode', { is: 'BE', then: Yup.string()...}
)
both fields should be at the sibling level.
I am using joi-browser 13.4.0. In order to generate error message for each input field I am trying to validate fields using .required() like so:
config = {
input1: Joi.string()
.empty("")
.required(),
input2: Joi.string()
.empty("")
.required()
};
schema = Joi.object(this.config).xor("input1", "input2");
But this example is invalid because when input1 or input2 is set to .required(), .xor() function is being ignored. Is there any other way to implement XOR validation without using .xor() method?
Thanks.
You don't need required() if you're using xor:
config = {
input1: Joi.string().empty(""),
input2: Joi.string().empty("")
};
schema = Joi.object(config).xor("input1", "input2");
In fact, using required() like that would never validate. You'd get one of the following error messages:
ValidationError: child "input1" fails because ["input1" is required]
or
ValidationError: "value" contains a conflict between exclusive peers [input1, input2]
Use object.length()
Is there any other way to implement XOR validation without using .xor() method?
Yes, you could for example use the object().length() property to limit the keys in an object to 1.
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).required().length(1);
const value = {
input1: "input1",
};
// this will fail
// const value = {};
// this will fail too
// const value = {
// input1: 'input1',
// input2: 'input2',
// };
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));
Be careful
Don't forget to add required() to the parent object, otherwise it is possible to pass undefined to the validation function!
Without required() on the parent it is possible that a simple undefined will pass the validation:
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).length(1); // no required()
const value = undefined; // this will pass validation
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));
How to implement the condition like value of 1 field should always be greater than the value of another field.
here's my schema
value: Yup.number().required(''),
value2: Yup.number().when('value',{
is: (val) => something here
then: Yup.number().required('Required').positive('value should be positive'),
otherwise: Yup.number()
})
I want to check for value2 to always be > value.
How to access value2's value in the when condition?
I'm not sure it's the right way to do it, but I'm using test.
Like this:
yourField: Yup.string().test('should-be-greather-than-yourField2', 'Should be greather than yourfield 2', function(value) {
const otherFieldValue = this.parent.yourField2
if (!otherFieldValue) {
return true;
}
return value > otherFieldValue;
})
Try this if you want to compare more than condition between two fields
import * as Yup from 'yup';
value: Yup.number().required(''),
value2: Yup.number().required()
.moreThan(
Yup.ref('value'),
'Should be more than value2'
)
I'm not sure, but try is: (val) => Yup.number().moreThan(val)
I am validating same fields for email and mobile no so user can validate both is same field.
username: yup
.string().when({
is : value =>isNaN(value),
then: yup.string().required('email/mobileno is required') .matches( Regex.EMAIL_REGX,
'Invalid email',
),
otherwise: yup.string()
.matches(Regex.PHONENO_REGX, StringUtils.phoneNo)
}),