Yup: Nested object validation with when not working properly.

Created on 3 Jan 2020  路  5Comments  路  Source: jquense/yup

When i'm trying to validate a nested object. within when, it is not validating the request properly.

const updateRecord = Yup.object().shape({
    title: Yup.string()
        .required()
        .label("Post title"),
    description: Yup.string()
        .required()
        .label("Post description"),
    is_update: Yup.boolean()
        .default(false)
        .required(),
    images: Yup.object().shape({
        logoImage: Yup.mixed().when("is_update", {
            is: true,
            then: Yup.mixed()
                .nullable()
                .test("fileFormat", "Only .png file allowed.", value => {
                    if (!value) {
                        return true;
                    }
                    return value && ["image/png"].includes(value.type);
                })
                .label("Logo image"),
            otherwise: Yup.mixed()
                .required()
                .test("fileFormat", "Only .png file allowed.", value => {
                    if (!value) {
                        return true;
                    }
                    return value && ["image/png"].includes(value.type);
                })
                .label("Logo image")
        }),
        backgroundImage: Yup.mixed().when("is_update", {
            is: true,
            then: Yup.mixed()
                .nullable()
                .test("fileFormat", "Only .png file allowed.", value => {
                    if (!value) {
                        return true;
                    }
                    return value && ["image/png"].includes(value.type);
                })
                .label("Background image"),
            otherwise: Yup.mixed()
                .required()
                .test("fileFormat", "Only .png file allowed.", value => {
                    if (!value) {
                        return true;
                    }
                    return value && ["image/png"].includes(value.type);
                })
                .label("Background image")
        })
    })
});

Here i'm validating form based on is_update key which can be either true or false. but I'm getting it undefined. i have tried to console using this.

is: value => {
    console.log(value);
    return true;
}

It is logging is_update as undefined.

Most helpful comment

A workaround I found is to use Yup.mixed.test()

Example

Yup.object<any>().shape({
    name: Yup.string(),
    gender: Yup.boolean(),
    car: Yup.object<any>().shape({
        model: Yup.string(),
        horsePower: Yup.number(),
        color: Yup.mixed().test(
            'testName',
            'My error message',
            function (this: TestContext, fieldValue: any): boolean {
                const objectToValidate = this.options.context.data; // This is the whole object that comes to the validation
                /*
                    Looks like this
                    {
                        name: 'nameToValidate',
                        gender: true,
                        car: {
                            model: 'Ford',
                            horsePower: 225,
                            color: 'red',
                        }
                    }
                */

                // Now you can take the property you need
                // and continue by custom validating it and returning 'true'
                // when it passes or 'false' when it fails
            },
        ),
    }),
});

Also, good to note that instead of false you can return this.createError({ message: "YourCustomErrorMessage" }); if you have different failures in the test.

All 5 comments

refs (which are used in conditions) resolve values that are being validated, they do not resolve schemas (which is where your default value is coming from).

You can solve this quickly in your case by doing this:

... 
logoImage: Yup.mixed().when("is_update", {
   is: value => !!value, // instead of `is: true`
...

The real question here is - should/could refs resolve schemas?. Right now there's only options.parent available to the ref, but if options.parentSchema were available, this (I think) would be a much less complicated question to answer.

Maintain reference to the parent schema?

@akmjenkins I already have tried your solution. but the thing is that value is coming as undefined only if i'm doing it in nested object. if i'll compare it in parent object, everything works fine.

...
logoImage: Yup.mixed().when("is_update", {
is: value => !!value, // instead of is: true
...
```

Maintain reference to the parent schema?

@ashishtechuz, man have I embarrassed myself. The schema will be cast, so the default will be obeyed, but you're right. The real issue is that you want to access an ancestor, not a sibling. That isn't supported right now. Attempts were made here and here but the general fix right now is to pass values as context so, regardless of the nested level of your condition, you'll have access to them.

I think you can probably do it like this:

   myValidationSchema.validate(yourObject,{context:myValidationSchema.cast(yourObject)});

And then change your nested condition to use:

... 
logoImage: Yup.mixed().when("$is_update", {
   is: true
...

EDIT:

I think the general goal might be able to support relative/absolute paths like this:

object({
   is_update:boolean().default(false),
  something:object({
     field: mixed().when('../is_update',...) // OR
     field2: mixed().when('/is_update',...) // OR
  })
})

A workaround I found is to use Yup.mixed.test()

Example

Yup.object<any>().shape({
    name: Yup.string(),
    gender: Yup.boolean(),
    car: Yup.object<any>().shape({
        model: Yup.string(),
        horsePower: Yup.number(),
        color: Yup.mixed().test(
            'testName',
            'My error message',
            function (this: TestContext, fieldValue: any): boolean {
                const objectToValidate = this.options.context.data; // This is the whole object that comes to the validation
                /*
                    Looks like this
                    {
                        name: 'nameToValidate',
                        gender: true,
                        car: {
                            model: 'Ford',
                            horsePower: 225,
                            color: 'red',
                        }
                    }
                */

                // Now you can take the property you need
                // and continue by custom validating it and returning 'true'
                // when it passes or 'false' when it fails
            },
        ),
    }),
});

Also, good to note that instead of false you can return this.createError({ message: "YourCustomErrorMessage" }); if you have different failures in the test.

his.createError({ message: "YourCustomErrorMessage" });

Cannot thank you enough for this detail!

Was this page helpful?
0 / 5 - 0 ratings