I am using Formik with Yup for validation and TypeScript
I have a field that needs to validate based on the value of another field.
The first field is called price and the second field is called tips. The max tip value is 10% of the what ever the price entered is.
I tried to create validation for this using the following:
tips: yup.number()
.min(0, `Minimum tip is $0`)
.max( parseFloat(yup.ref('price'))* 0.1, "Maximum tip is 10% of the price.");
however this doesn't compile because yup.ref returns a Ref. How can I get the value of the price field in this validation?
number.max cannot reference other field and calculate with it at validation.
If you want to do this, you need to implement own schema with mixed.test.
Here is a example.
tips: number()
.min(0, `Minimum tip is $0`)
.test({
name: 'max',
exclusive: false,
params: { },
message: '${path} must be less than 10% of the price',
test: function (value) {
// You can access the price field with `this.parent`.
return value <= parseFloat(this.parent.price * 0.1)
},
}),
Here is doc.
You can check and try how it works here.
I hope this will help you.
if you don't want to use this.parent to access the other properties values you can use the context.
tips: number()
.min(0, `Minimum tip is $0`)
.test(
'max',
'${path} must be less than 10% of the price',
(value, context) => value <= parseFloat(context.parent.price * 0.1),
),
// OR
tips: number()
.min(0, `Minimum tip is $0`)
.test({
name: 'max',
exclusive: false,
params: { },
message: '${path} must be less than 10% of the price',
test: (value, context) => value <= parseFloat(context.parent.price * 0.1),
}),
For referencing other field value we can use this.parent or ctx.parent in case if our value is not nested.
object({
title: string(),
price: string().test('test-name', 'test-message', (value, ctx) => {
let title = ctx.parent.title;
}),
foo: string()
.test('test-name1', 'test-message1', function (value) {
let title = this.parent.title
})
})
but if we have nested value parent is going to give parent of nested
value. in this case parent is not going to work if we want to access
very parent value. we can access parent value with ctx.from. ctx.from contains parents from bottom to top. for example:
object({
title: string(),
ourObject: object({
param1: string(),
param2: string()
.test('test-name', 'test-msg', (value, ctx) => {
let title = ctx.from[ctx.from.length - 1].value.title
})
})
})
or we can easily access any data we want with providing context to schema when validating
object({
price: string(),
foo: string()
.test('test-name', 'test-message', (value, ctx) => {
let arr = ctx.context.arr;
})
})
.validate({ price: 5, foo: 'boo' }, { context: { arr: [1, 2] } })
.then(() => ...)
.catch((err) => ...)
doc
LIKE SO
.test((val,obj)=>{
console.log(val,obj.parent.otherField)
})
val=>current field value
obj=>object of the whole schema
EXAMPLE::::
date: yup.
string()
.required()
.test('type', 'Date should be future date', (val) => {
console.log(val);
}),
time: yup
.string()
.required()
.test('type', 'Time should be future time', (val,obj) => {
console.log(val,'in time val',obj.parent.date,'date')
}),
Related
I would like to only add the values to the return object if they hold a value otherwise I don't even want to send them. I know this can be achieved with if statement, but how would I do this if there were even more?
As you can see based on the required key being true, in the code I have a few of these arguments that can but don't actually need to exist. I'd like to avoid adding them if that is true.
I am using MongoDB so if I don't need to add the field I don't want to have to, but the only way I know how to achieve this is by writing a bunch of if statements to check if they exist. Is there a better way to do this?
The reason why MongoDB is relevant is that MongoDB is a no SQL database so just because something is an option doesn't mean it has to exist.
What i tried so far:
I tried returning undefined if the item doesn't exist this does not work however Example Below
resolve: (_, { input: { createdBy, name, date, description, cost, link, weights, eventApplicants } }) => {
return dbMutations.createEvent({
createdBy, name, date, description,
cost: cost ? cost : undefined, link: link ? link : undefined, weights: weights && JSON.parse(weights),
eventApplicants: eventApplicants && JSON.parse(eventApplicants)
})
}
Code:
You really just need to look at the resolve function, but I put the rest of the code there for reference.
createEvent: t.fieldWithInput({
input: {
createdBy: t.input.field({
type: 'mongoId',
required: true
}),
name: t.input.string({
required: true
}),
date: t.input.field({
type: 'Date',
required: true
}),
description: t.input.string({
required: true
}),
cost: t.input.string(),
link: t.input.string(),
weights: t.input.string(),
applicants: t.input.string(),
},
type: 'mongoId',
// #ts-ignore
resolve: (_, {
input: {createdBy, name, date, description, cost, link, weights, applicants}
}) => {
let parsedWeights
let parsedApplicants
if (weights) {
parsedWeights = JSON.parse(weights)
}
if (applicants) {
parsedApplicants = JSON.parse(applicants)
}
return dbMutations.createEvent({
createdBy,
name,
date,
description,
cost,
link,
weights: parsedWeights,
applicants: parsedApplicants
})
}
})
My Idea:
I think what would work is just assigning these variables to an arbitrary object. Once we do that we can do some kind of object map and set the result to the return statement.
Here's a thing that removes null or undefined values. You may want to tweak the condition to take out empty strings or other falsy things.
const clean = obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v != null)
);
let o = { a: 0, b: null, c: '' };
console.log( clean(o) )
With it, the resolve function can say...
resolve: (_, {
input: {createdBy, name, date, description, cost, link, weights, applicants}
}) => {
// or promote this in scope to use elsewhere...
const clean = obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v != null)
);
const event = clean({
createdBy,
name,
date,
description,
cost,
link,
weights: weights ? JSON.parse(weights) : null,
applicants: applicants ? JSON.parse(applicants) : null
});
console.log(event); // examine this to prove event is the object you want
return dbMutations.createEvent(event)
}
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 two strings of date start_date and end_date. Below have empty string as there initial value:
export interface FilterSchema {
start_date?: any;
end_date?: any;
}
const initialValues: FilterSchema = {
start_date: '',
end_date: '',
};
Initially they both are empty. But if one of them is selected, then other also needs to be selected. If none of them is selected then there is no need of validation. So I used yup for this in following way:
const filterSchema = yup.object().shape({
start_date: yup.string().when('end_date', {
is: value => value && value != '',
then: yup.string().required('Required'),
}),
end_date: yup.string().when('start_date', {
is: value => value && value != '',
then: yup.string().required('Required'),
}),
});
But I am getting error message:
Cyclic dependency, node was: "end_date"
I am using filterSchema in Formik:
<Formik
onSubmit={facilityFilter}
validationSchema={filterSchema}
....
Fields that depend on another field need to be constructed in the right order, i.e. If endDate depends on startDate then startDate needs to go first, however in your case you want to validate after everything is constructed. Yup isn't able to figure this out so it throws the cyclic dependency error.
If you want to tell Yup to ignore the ordering you can add an array of field pairs to the second argument in the shape method
const filterSchema = yup.object().shape({
start_date: yup.string().when('end_date', {
is: value => value && value != '',
then: yup.string().required('Required'),
}),
end_date: yup.string().when('start_date', {
is: value => value && value != '',
then: yup.string().required('Required'),
}),
}, [["start_date", "end_date"]]);
It's not the best solution for dealing with cyclical dependencies as noted here by the creator of yup: https://github.com/jquense/yup/issues/720
In mongoose.model, I have chosen the type of name to be a string and the type of age to be a number, but when I enter a number as the value of name, I don't get an error and the same thing happens when I use something like '18' as the value of age.
Here is the code:
const User = mongoose.model('User', {
name: { type: String },
age: { type: Number }
});
const me = new User({
name: 12,
age: '18'
});
me.save().then(() => console.log(me)).catch(error => console.log(error));
Mongoose casts the values to the corresponding type, if it fails a CastError is thrown, from the doc:
Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called casting the document. If casting
fails for a given path, the error.errors object will contain a CastError object.
You can try this by given age the value 'aa' for example.
If you want to override this behavior you can use one of the following options:
Disable casting globally: mongoose.Number.cast(false)
Disable casting just for a given path:
age: {
type: Number,
cast: false // Disable casting just for this path
},
Use a custom function:
age: {
type: Number,
cast: v => { return typeof v === 'number' && !isNaN(v) ? Number(v) : v; } // Override casting just for this path
}
I'm trying to define a Yup validation for an object - if a defined sibling is set to true, the field of type object should be required, otherwise not
Example:
const { object, string, number, date, boolean } = require('yup')
const contactSchema = object({
isBig: boolean(),
count: number()
.when('isBig', {
is: true, // alternatively: (val) => val == true
then: number().min(5),
otherwise: number().min(0),
}),
complexOne: object({
simpleOne: string(),
})
.when('isBig', {
is: true,
then: object().required(),
otherwise: object(),
})
})
The object passed into the validation:
{
isBig: true,
count: -1,
}
As you can see, I intentionally don't pass the complexOne since I want Yup to display the error. The validation for the count works correctly - if the value is less than 0 and the isBig is set to true, Yup will correctly display an error message ValidationError: count must be greater than or equal to 5
Unfortunately, it completely ignores the conditional validation set for the complexOne field. Either yup does not support the when for object types or I'm doing something incorrectly.
Thanks for any help
you must set strict option to true in order to only validate the object, and skip any coercion or transformation:
contactSchema.validate(contact, { strict: true })
.then(obj => {
console.log(obj)
}, err => {
console.log(err.message)
})
Demo: