Yup: Require only one of 4 fields

Created on 12 Jul 2018  路  7Comments  路  Source: jquense/yup

validationSchema={
  Yup.object().shape({
    emailAddress: Yup.string().email('Please enter a valid email address'),
    phoneHome: Yup.number().typeError('Home Phone must be a number'),
    phoneMobile: Yup.number().typeError('Mobile Phone must be a number'),
    phoneWork: Yup.number().typeError('Work Phone must be a number'),
  })
  .test(
    'at-least-one-contact',
    'You must provide at least one contact method',
    value => {
      const test = !!(value.emailAddress || value.phoneHome || value.phoneMobile || value.phoneWork);
      return Yup.ValidationError(['emailAddress', 'phoneHome', 'phoneMobile', 'phoneWork'], 'one is required', '');
    }
  )
}

Basically I am getting an error on the value => { definition saying

Type 'ValidationError' is not assignable to type 'boolean | Promise<boolean>'.

Is there an easy way to do this so only one field is required?

Most helpful comment

I use this:

const Yup = require('yup')

Yup.addMethod(Yup.object, 'atLeastOneOf', function(list) {
  return this.test({
    name: 'atLeastOneOf',
    message: '${path} must have at least one of these keys: ${keys}',
    exclusive: true,
    params: { keys: list.join(', ') },
    test: value => value == null || list.some(f => value[f] != null)
  })
})

const Schema = Yup.object()
  .shape({
    one: Yup.number(),
    two: Yup.number(),
    three: Yup.number()
  })
  .atLeastOneOf(['one', 'two'])

Schema.isValidSync({ three: 3 }) // false
Schema.isValidSync({ one: 1, three: 3 }) // true

All 7 comments

Tried this aswell but get the cyclic error

Yup.object().shape({
  emailAddress: Yup.string()
    .email('Please enter a valid email address')
    .when(['phoneHome', 'phoneMobile', 'phoneWork'], {
      is: (phoneHome, phoneMobile, phoneWork) => !phoneHome && !phoneMobile && !phoneWork,
      then: Yup.string().required('Atleast one contact field is required.'),
    }),
  phoneHome: Yup.number()
    .typeError('Please enter a number.')
    .when(['emailAddress', 'phoneMobile', 'phoneWork'], {
      is: (emailAddress, phoneMobile, phoneWork) => !emailAddress && !phoneMobile && !phoneWork,
      then: Yup.number().required('Atleast one contact field is required.'),
    }),
  phoneMobile: Yup.number()
    .typeError('Please enter a number.')
    .when(['emailAddress', 'phoneHome', 'phoneWork'], {
      is: (phoneHome, emailAddress, phoneWork) => !phoneHome && !emailAddress && !phoneWork,
      then: Yup.number().required('Atleast one contact field is required.'),
    }),
  phoneWork: Yup.number().typeError('Work Phone must be a number')
    .typeError('Please enter a number.')
    .when(['emailAddress', 'phoneHome', 'phoneMobile'], {
      is: (phoneHome, phoneMobile, emailAddress) => !phoneHome && !phoneMobile && !emailAddress,
      then: Yup.number().required('Atleast one contact field is required.'),
  }),
})

Edit:

In the mean time going to use <Formik /> validate method to just do all my validation. Preferably will get the schema working eventually. But can't be blocked by this work for too long unfortunately. Will be back on it next week. Or if someone manages to sort this out overnight haha

Please see previous issues on deal with cyclic errors thanks!

@jquense have tried the following ones too with no resolution unfortunately.

#193
#176
#195

:(

@pmonty I've made an atLeastOneRequired helper https://runkit.com/anber/yup---at-least-one

I use this:

const Yup = require('yup')

Yup.addMethod(Yup.object, 'atLeastOneOf', function(list) {
  return this.test({
    name: 'atLeastOneOf',
    message: '${path} must have at least one of these keys: ${keys}',
    exclusive: true,
    params: { keys: list.join(', ') },
    test: value => value == null || list.some(f => value[f] != null)
  })
})

const Schema = Yup.object()
  .shape({
    one: Yup.number(),
    two: Yup.number(),
    three: Yup.number()
  })
  .atLeastOneOf(['one', 'two'])

Schema.isValidSync({ three: 3 }) // false
Schema.isValidSync({ one: 1, three: 3 }) // true

@calmattack,

I had a similar situation and solved it with the following code.

My solution requires selecting other input elements and checking the value. With more time you could probable adept @risenforces solution to pass in the schema for validation instead of selecting elements.

yup.addMethod(yup.string, 'requiredIf', function(list, message) {
  return this.test('requiredIf', message, function(value) {
    const { path, createError } = this;

    // check if any in list contain value
    // true : one or more are contains a value
    // false: none contain a value
    var anyHasValue = list.some(value => {

      // return `true` if value is not empty, return `false` if value is empty
      return Boolean(document.querySelector(`input[name="${value}"]`).value);

    });

    // returns `CreateError` current value is empty and no value is found, returns `false` if current value is not empty and one other field is not empty.
    return !value && !anyHasValue
      ? createError({ path, message })
      : true;
  });
});


const schema = yup.object().shape({
  email: yup.string().requiredIf(['work','home','cell']),
  work: yup.string().requiredIf(['email','home','cell']),
  home: yup.string().requiredIf(['email','work','cell']),
  cell: yup.string().requiredIf(['email','work','home']),
});

I use this:

const Yup = require('yup')

Yup.addMethod(Yup.object, 'atLeastOneOf', function(list) {
  return this.test({
    name: 'atLeastOneOf',
    message: '${path} must have at least one of these keys: ${keys}',
    exclusive: true,
    params: { keys: list.join(', ') },
    test: value => value == null || list.some(f => value[f] != null)
  })
})

const Schema = Yup.object()
  .shape({
    one: Yup.number(),
    two: Yup.number(),
    three: Yup.number()
  })
  .atLeastOneOf(['one', 'two'])

Schema.isValidSync({ three: 3 }) // false
Schema.isValidSync({ one: 1, three: 3 }) // true

The check in that test doesn't work well for strings because it allows an empty string. To support strings, you can change it to:

    test: value => value == null || list.some(f => !!value[f])
Was this page helpful?
0 / 5 - 0 ratings