Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yup validate array of objects contains at most one object where property = value

I'm using Formik's FieldArray to add objects dynamically to an array, rendering additional form elements as objects are push()ed to the array.

My schema looks like this:

const EMAIL_SCHEMA = Yup.object().shape({
  address: Yup.string().email().required( 'E-mail address is required.' ),
  isPreferredContact: Yup.boolean()
})

const SCHEMA = Yup.object().shape({
  emails: Yup.array()
             .of( EMAIL_SCHEMA )
             .ensure()
             .compact( v => !v.address )
             .required( 'At least one e-mail address is required.' )
})

For each e-mail input, there is a corresponding checkbox to indicate if it is the preferred contact e-mail address. No e-mail addresses are required to be marked as preferred.

What I would like to do is validate that the array contains at most one object where isPreferredContact is true. If there are 3 e-mail objects in the array and isPreferredContact is false for all of them, that's a valid state. That is to say:

let values = [
  {address: '[email protected]', isPreferredContact: false},
  {address: '[email protected]', isPreferredContact: false},
  {address: '[email protected]', isPreferredContact: false}
] // OK

let values = [
  {address: '[email protected]', isPreferredContact: true},
  {address: '[email protected]', isPreferredContact: false},
  {address: '[email protected]', isPreferredContact: false}
] // OK

let values = [
  {address: '[email protected]', isPreferredContact: true},
  {address: '[email protected]', isPreferredContact: true},
  {address: '[email protected]', isPreferredContact: false}
] // Invalid

I see this answer

Yup: deep validation in array of objects

shows that the compact() method can be used to validate for at least one, because if after removing "falsy" values from the array, the array is empty, then it's easy to treat the schema key as invalid.

There's nothing I can see, though, for validating that the array contains at most one object with a property = value predicate.

Is there a way to do this?

like image 582
diekunstderfuge Avatar asked Jun 24 '20 21:06

diekunstderfuge


People also ask

How do you validate two fields that depend on each other with Yup?

Solution: const yup = require('yup') const { setLocale } = yup setLocale({ mixed: { notType: 'the ${path} is obligatory', required: 'the field ${path} is obligatory', oneOf: 'the field ${path} must have one of the following values: ${values}' } }) const myNameSchema = yup. object(). shape({ first_name: yup.

How does Yup validation work?

The validation library Yup allows you to pass in values, which aren't validated themselves, to aid in validation using a context. This is useful for making sure a reference to another object is valid, by passing in an instance of the other object.

Can you use Yup without Formik?

up is a JavaScript object schema validator and object parser. It is not required to use Formik.


1 Answers

After poking through Yup's issues in GitHub, staring at the API docs (the doc for addMethod is really terrible), and testing in Code Sandbox, I found that this works:

Yup.addMethod(Yup.array, 'atMostOne', function(args) {
  const { message, predicate } = args
  return this.test('atMostOne', message, function(list) {
    // If there are 2+ elements after filtering, we know atMostOne must be false.
    return list.filter(predicate).length < 2
  })
})

The predicate, obviously, is a function that takes an element of the array and performs a test on it that returns a boolean.

For an array of scalar values, this is as simple as el => el === value. For an array of objects, it would be el => el.property === value or el[property] === value.

Hope this helps anybody else curious about this.

like image 137
diekunstderfuge Avatar answered Sep 22 '22 10:09

diekunstderfuge