DEV Community

Cover image for How to validate two fields that depend on each other with Yup
Gabriel Terriaga
Gabriel Terriaga

Posted on

How to validate two fields that depend on each other with Yup

Hey guys, let's get straight to the point, I'll show you how to Yup validate two fields that depend on each other.

You may have faced this problem already. If not, you'll face it.

Here is an example of what will happen:

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.string().ensure().when('surname', {
    is: '',
    then: yup.string().required()
  }),
  surname: yup.string().ensure().when('first_name', {
    is: '',
    then: yup.string().required()
  })
})

[...]

Error: Cyclic dependency, node was:"surname"
at visit (/home/{yourPath}/node_modules/toposort/index.js:45:13)
at visit (/home/{yourPath}/node_modules/toposort/index.js:62:9)
at visit (/home/{yourPath}/node_modules/toposort/index.js:62:9)
at Function.toposort [as array]...


Cyclic error, but how are we going to solve this?

Fields that depend on each other to be validated need to sorted so they are "constructed" in the correct order, e.g. if depend on field A in field B, you needs to cast and coerce the value in field A before it's handed to B.

What is happening here is that we are just adding a validation to the condition, so there is really no need to request anything for the validation to happen after everything is constructed already. Due to the flexibility and programmatic nature of Yup it can't distinguish between those two cases.


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.string().ensure().when('surname', {
    is: '',
    then: yup.string().required()
  }),
  surname: yup.string().ensure().when('first_name', {
    is: '',
    then: yup.string().required()
  })
}, [['surname', 'first_name']]) // <--- adding your fields which need validation

[...]
{
    "message": "there was an error validating data",
    "error": [
        "the field first_name is obligatory",
        "the field surname is obligatory"
    ]
}

Another example with extra fields:

[...]
const myNameSchema = yup.object().shape({
  first_name: yup.string().when(['surname', 'age'], {
    is: (surname, age) => !surname && !age,
    then: yup.string().required()
  }),
  surname: yup.string().when(['first_name', 'age'], {
    is: (first_name, age) => !first_name && !age,
    then: yup.string().required()
  }),
  age: yup.number().when(['first_name', 'surname'], {
    is: (first_name, surname) => !first_name && !surname,
    then: yup.number().required()
  })
}, [
  ['first_name', 'surname'], // <--- adding your fields which need validation
  ['first_name', 'age'],
  ['surname', 'age']
])

[...]
{
    "message": "there was an error validating data",
    "error": [
        "the field first_name is obligatory",
        "the field surname is obligatory",
        "the field age is obligatory"
    ]
}

I hope I helped you! 😉
Thank you!!


My GitHub 😎

Discussion (4)

Collapse
spideyinf profile image
Phuc Hoang

Thanks a ton!!!

Collapse
alifarhad profile image
Farhad Ali

thank you so much. I literally spent 3 hours trying to find a solution for this stupid error. finally this solved it. THANK YOU!

Collapse
trandinhhiep789 profile image
eltr

Wow it look good, but i cannot get it at
is: (surname, age) => !surname && !age
what does it mean? thanks

Collapse
cmubo profile image
cmubo

Its checking that when surname and age is a falsy value (false, empty string, undefined, 0, etc), use the then: yup.string().required().