Either no field is required or all fields are required if any of the field is non-empty in yup Schema validation

Viewed 573

Scenario: i have 4 fields and i want to validate using Yup validation schema

  • Suppose if the user enters value for any of the above 1 field, the other 3 must be required

  • if the user does not enter value for any of the 4 fields then no field is required

  • thus all i want to say is No fields are required if all fields are empty and suppose any one field is non-empty then all fields are required!

Solution i tried

const validationSchema = Yup.object({
  field1: Yup.mixed().when(["field2", "field3", "field4"], {
    is: (...fields) => fields.some(Boolean),
    then: Yup.mixed().required(),
    otherwise: Yup.mixed().notRequired()
  }),
  field2: Yup.mixed().when(["field1", "field3", "field4"], {
    is: (...fields) => fields.some(Boolean),
    then: Yup.mixed().required(),
    otherwise: Yup.mixed().notRequired()
  }),
  field3: Yup.mixed().when(["field2", "field1", "field4"], {
    is: (...fields) => fields.some(Boolean),
    then: Yup.mixed().required(),
    otherwise: Yup.mixed().notRequired()
  }),
  field4: Yup.mixed().when(["field2", "field3", "field1"], {
    is: (...fields) => fields.some(Boolean),
    then: Yup.mixed().required(),
    otherwise: Yup.mixed().notRequired()
  })
});

Error i got

  • Cyclic dependency, node was:"field4"
1 Answers

This question is over a month old when writing this answer, but maybe it could help someone or at least point them in the right direction.

I had the Cyclic issue, but I had three fields. I wanted either non of the fields to be entered, or all three must be required.

This link: https://github.com/jquense/yup/issues/176#issuecomment-369925782 helped but did not work for me. But after a few tweaks, I made it work; here is what worked for me.

const SettingsSchema = Yup.object().shape({
    password: Yup.string().ensure()
      .when(['new_password', 'confirm_password'], {
        is: (new_password, confirm_password) => new_password !== '' || confirm_password !== '',
        then: (SettingsSchema) => SettingsSchema.required(t('Password is required.')),
      }),
    new_password: Yup.string().ensure()
      .when(['password', 'confirm_password'], {
        is: (password, confirm_password) => password !== '' || confirm_password !== '',
        then: (SettingsSchema) => SettingsSchema.required(t('New Password is required.'))
          .min(8, t('Password cannot be shorted than 8 characters.')),
      }),
    confirm_password: Yup.string().ensure()
      .when(['password', 'new_password'], {
        is: (password, new_password) => password !== '' || new_password !== '',
        then: (SettingsSchema) => SettingsSchema.required(t('Confirm Password is required.'))
          .oneOf([Yup.ref('new_password')], t('New Password and Confirm Password must match.')),
      }),
  }, [['password', 'new_password'], ['password', 'confirm_password'], ['new_password', 'confirm_password']]);

What was required to remove the cyclic error and make the validation work was:

  • The last parameter of shape it's an array containing arrays of pairs.

The documentation on that part is not very clear at the time of writing. https://github.com/jquense/yup#objectshapefields-object-nosortedges-arraystring-string-schema

Things to note:

  1. On each field, I call .ensure(). This ensures that if values are undefined or null, they will be transformed into empty strings.
  2. Because you have four fields, you will need an extra pair in the array of pairs of fields as the last parameter of shape.

So I think your example above could be rewritten as:

const schema = Yup.object().shape({
  field1: Yup.mixed().ensure().when(["field2", "field3", "field4"], {
    is: (field2, field3, field4) => field2 !== '' || field3 !== '' || field4 !== '',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired()
  }),
  field2: Yup.mixed().ensure().when(["field1", "field3", "field4"], {
    is: (field1, field3, field4) => field1 !== '' || field3 !== '' || field4 !== '',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),
  field3: Yup.mixed().ensure().when(["field1", "field2", "field4"], {
    is: (field1, field2, field4) => field1 !== '' || field2 !== '' || field4 !== '',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),
  field4: Yup.mixed().ensure().when(["field1", "field2", "field3"], {
    is: (field1, field2, field3) => field1 !== '' || field2 !== '' || field3 !== '',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  })
}, [["field1", "field2"], ["field2", "field3"], ["field3", "field4"], ["field1", "field4"]]);

I know the example with three fields works as I am using it in my code. The example with four parameters is derived from the example of three parameters and I did not test it so I am not sure is totally correct.

Related