Yup: How to apply different validation to each element of an array?

Created on 1 May 2019  Â·  7Comments  Â·  Source: jquense/yup

I have an array of three numbers: [min, mid, max], and I want to apply different validation function(s) to each element of the array. I don’t quite know how to achieve this with Yup.

Example use cases:

1) min: no validation; mid: validate that mid > min; max: validate that max > mid

-or-

2) min: required; mid: not required; max: required

I know something like array().of(number()…) will apply the same validation to each element in the array, but this is not what I want. Each element in the array has its own validation requirements.

Thanks.

Most helpful comment

@jquense I am actually using Yup in the context of Formik validation. I was able to implement the behavior I was after using Yup's mixed.test() API. So the validation schema looks something like:

const schema = Yup.object().shape({
  values: Yup.array(),
  'values[0]': Yup.number().test('test-0', 'test-msg', function() {
    // test for values[0]
    return this.parent && this.parent.values && this.parent.values[0] !== undefined
  }),
  'values[1]': Yup.number().test('test-1', 'test-msg', function() {
    // test for values[1]
    return this.parent && this.parent.values && this.parent.values[1] > this.parent.values[0]
  }),
  'values[2]': Yup.number().test('test-2', 'test-msg', function() {
    // test for values[2]
    return this.parent && this.parent.values && this.parent.values[2] > this.parent.values[1]
  })
})

Basically, in test(...), I can get at the individual array values, and implement custom test logic.

Thanks for the help.

All 7 comments

Not sure if it'd work but, did you try treating the array as an object?
Something like:

const schema = object().shape({
  0: string(), 
  1: number(),
  2: boolean(),
  length: number()
})

What you seem to be looking for is basic tuple support, which isn't what array is meant to handle. I'd recommend generally that you turn your tuple into an object like {min, mid, max} if you can, since you'd be able to define individual schema for each. Otherwise you could create a custom tuple schema type that extends array but has a different signature.

@eddyw thanks for the suggestion. I tried it out but did not get the behavior that I was looking for.

@jquense I am actually using Yup in the context of Formik validation. I was able to implement the behavior I was after using Yup's mixed.test() API. So the validation schema looks something like:

const schema = Yup.object().shape({
  values: Yup.array(),
  'values[0]': Yup.number().test('test-0', 'test-msg', function() {
    // test for values[0]
    return this.parent && this.parent.values && this.parent.values[0] !== undefined
  }),
  'values[1]': Yup.number().test('test-1', 'test-msg', function() {
    // test for values[1]
    return this.parent && this.parent.values && this.parent.values[1] > this.parent.values[0]
  }),
  'values[2]': Yup.number().test('test-2', 'test-msg', function() {
    // test for values[2]
    return this.parent && this.parent.values && this.parent.values[2] > this.parent.values[1]
  })
})

Basically, in test(...), I can get at the individual array values, and implement custom test logic.

Thanks for the help.

@otori23 is there a mistake in your example ?
In need to handle a very similar case as yours but cant make it work.

 values: Yup.array(), <=== HERE
  'values[0]': Yup.number().test('test-0', 'test-msg', function() {

Thank you so much

@jquense I am actually using Yup in the context of Formik validation. I was able to implement the behavior I was after using Yup's mixed.test() API. So the validation schema looks something like:

const schema = Yup.object().shape({
  values: Yup.array(),
  'values[0]': Yup.number().test('test-0', 'test-msg', function() {
    // test for values[0]
    return this.parent && this.parent.values && this.parent.values[0] !== undefined
  }),
  'values[1]': Yup.number().test('test-1', 'test-msg', function() {
    // test for values[1]
    return this.parent && this.parent.values && this.parent.values[1] > this.parent.values[0]
  }),
  'values[2]': Yup.number().test('test-2', 'test-msg', function() {
    // test for values[2]
    return this.parent && this.parent.values && this.parent.values[2] > this.parent.values[1]
  })
})

Basically, in test(...), I can get at the individual array values, and implement custom test logic.

Thanks for the help.

how about target path to last index
any example for lastIndex

maybe

const schema = Yup.object().shape({
  values: Yup.array(),
  'values[0]': Yup.number(),
  'values[lastIndex]': Yup.string()
})

@jquense I am actually using Yup in the context of Formik validation. I was able to implement the behavior I was after using Yup's mixed.test() API. So the validation schema looks something like:

const schema = Yup.object().shape({
  values: Yup.array(),
  'values[0]': Yup.number().test('test-0', 'test-msg', function() {
    // test for values[0]
    return this.parent && this.parent.values && this.parent.values[0] !== undefined
  }),
  'values[1]': Yup.number().test('test-1', 'test-msg', function() {
    // test for values[1]
    return this.parent && this.parent.values && this.parent.values[1] > this.parent.values[0]
  }),
  'values[2]': Yup.number().test('test-2', 'test-msg', function() {
    // test for values[2]
    return this.parent && this.parent.values && this.parent.values[2] > this.parent.values[1]
  })
})

Basically, in test(...), I can get at the individual array values, and implement custom test logic.

Thanks for the help.

I'm using this approach to validate file uploads but I'm having issues.
More context here:
I'm using a single input field to upload many files using the multiple attribute. I want to validate each image file that it is of a certain type and size. Using your approach it works but with a big gotcha.

The minimum amount of file expected is one and a maximum of five. This means I have to write validations for the maximum number of files. Things seem to not work out if the maximum number of files is not uploaded; it returns an error.

For example

images: Yup.array().min(1).max(5).required("At least a file is required"),
    "images[0]": Yup.mixed().test("fileSize", "File too large", function () {
      return this.parent && this.parent.images &&  this.parent.images[0] && this.parent.images[0].size <= FILE_SIZE;
    })
    .test("fileFormat", "Your file format must be jpg, png, and jpeg", function () {
      return this.parent && this.parent.images &&  this.parent.images[0] && SUPPORTED_FORMATS.includes(this.parent.images[0].type);
    }),

    "images[1]": Yup.mixed().test("fileSize", "File too large", function () {
      return this.parent && this.parent.images &&  this.parent.images[1] && this.parent.images[1].size <= FILE_SIZE;
    })
    .test("fileFormat", "Your file format must be jpg, png, and jpeg", function () {
      return this.parent && this.parent.images &&  this.parent.images[1] && SUPPORTED_FORMATS.includes(this.parent.images[1].type);
    }),

If only one file is uploaded and meet the criteria , errors for the second file is thrown. What do I need to do to make sure that validation only runs for the number of uploaded files.?

Was this page helpful?
0 / 5 - 0 ratings