Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require only one of 4 fields #248

Closed
pmonty opened this issue Jul 12, 2018 · 10 comments
Closed

Require only one of 4 fields #248

pmonty opened this issue Jul 12, 2018 · 10 comments

Comments

@pmonty
Copy link

pmonty commented Jul 12, 2018

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?

@pmonty
Copy link
Author

pmonty commented Jul 12, 2018

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

@jquense
Copy link
Owner

jquense commented Jul 12, 2018

Please see previous issues on deal with cyclic errors thanks!

@jquense jquense closed this as completed Jul 12, 2018
@pmonty
Copy link
Author

pmonty commented Jul 12, 2018

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

#193
#176
#195

:(

@Anber
Copy link
Contributor

Anber commented Sep 25, 2018

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

@risen228
Copy link

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

@NathanHealea
Copy link

@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']),
});

@justincy
Copy link

justincy commented Dec 15, 2020

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])

@htr3n
Copy link

htr3n commented Aug 25, 2021

Updated:
I got into similar situation and used the following code as proposed by @risenforces and @justincy

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])
  })
})

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

I can see the validation returns false when all of the fields are blank or null, but I got no error message.

I tried the helper of @Anber

Yup.addMethod(Yup.object, 'atLeastOneRequired', function (list, message) {
  return this.shape(list.reduce((acc, field) => ({
    ...acc,
    [field]: this.fields[field].when(without(list, field), {
      is: (...values) => !values.some((item) => item),
      then: this.fields[field].required(message),
    }),
  }), {}), list.reduce((acc, item, idx, all) => [...acc, ...all.slice(idx + 1).map((i) => [item, i])], []));
});

and it produces the error message correctly.

Why doesn't the former code yield any error messages?

@hmmftg
Copy link

hmmftg commented Jun 22, 2022

Updated: I got into similar situation and used the following code as proposed by @risenforces and @justincy

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])
  })
})

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

I can see the validation returns false when all of the fields are blank or null, but I got no error message.

I tried the helper of @Anber

Yup.addMethod(Yup.object, 'atLeastOneRequired', function (list, message) {
  return this.shape(list.reduce((acc, field) => ({
    ...acc,
    [field]: this.fields[field].when(without(list, field), {
      is: (...values) => !values.some((item) => item),
      then: this.fields[field].required(message),
    }),
  }), {}), list.reduce((acc, item, idx, all) => [...acc, ...all.slice(idx + 1).map((i) => [item, i])], []));
});

and it produces the error message correctly.

Why doesn't the former code yield any error messages?

Hi

I used your code but got this error: Uncaught ReferenceError: without is not defined

@Vital-k
Copy link

Vital-k commented Jul 18, 2023

I used your code but got this error: Uncaught ReferenceError: without is not defined

"without" is from lodash library, in reactjs for example do:
import { without } from 'lodash';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants