jquense / yup

Dead simple Object schema validation
MIT License
22.96k stars 936 forks source link

Validate at least one checkbox (boolean) is chosen #72

Closed rosskevin closed 7 years ago

rosskevin commented 8 years ago
checkbox_fields_-_dummy_com
const schema = yup.object({
  red: yup.boolean(),
  orange: yup.boolean(),
  green: yup.boolean()
})

Implementing react-formal-material-ui, and trying to figure out how to require at least one of the options is chosen. How would you go about doing this? If it is custom/external, how to inject the validation error back into the react-formal form?

rosskevin commented 7 years ago

So one part is resolved - I can inject any errors not directly mapped to a react formal field name with the unmapped error display component in another issue.

I'm assuming I need to implement a custom validator on object which will iterate over the chosen keys, is that the most straight-forward way?

jquense commented 7 years ago

ya probably the best way. tbh tho I usually model the above as: color: string().oneOf(colors)

rosskevin commented 7 years ago

Got it, thanks.

DeBraid commented 7 years ago

Is there a better resource for using yup and checkbox's? Can't seem to find examples/documentation. Thanks!

I am struggling to implement both a checkbox and select element at the moment.

yantakus commented 7 years ago

@DeBraid

yup.object({
  terms: yup
    .boolean()
    .oneOf([true], 'Must Accept Terms and Conditions'),
});
lvdang commented 6 years ago

@rosskevin Can you show me a snipped of your code for the checkbox? I am trying to implement checkbox, having a hard time to setup the YUP schema.

lvdang commented 6 years ago

anyone know how i can use yup for multiple checkbox?

jquense commented 6 years ago

@lvdang I mentioned in the other issue you can use the react-widgets Selectlist and map your checkbox to an array. Otherwise how you model the schema is up to you, you can use a bunch of boolean fields, an array of values, whatever. The issue you are running into is that there is no "default" input for a group of checkboxes. What you use completely depends on what type of data you are representing as a group of checkboxes

lvdang commented 6 years ago

@jquense thanks for the input. I guess I am trying to figure out how yup works with react-formal and selectlist with error detection. How would I declare my yup schemas to map to selectlist for error detection? I guess that is my confusion.

berdyshev commented 6 years ago

Please, could someone share snippet how to validate that at least one checkbox is checked for the object? I have three different checkboxes with their own names (that's data model of the API) so the color: string().oneOf(colors) isn't work for me.

sbreiler commented 5 years ago

@berdyshev Something like this worked for me:

const schema = yup
  .object({
    red: yup.boolean(),
    orange: yup.boolean(),
    green: yup.boolean()
  })
  .test(
    'myCustomTest',
    null,
    (obj) => {
      if ( obj.red || obj.orange || obj.green ) {
        return true; // everything is fine
      }

      return new yup.ValidationError(
        'Please check one checkbox',
        null,
        'myCustomFieldName'
      );
    }
  );

Check it here

AElmoznino commented 5 years ago

@sbreiler I'm trying to figure out how to use your example, but also have a field that isn't a checkbox (e.g. an email address, so at least 1 checkbox has to be checked and the email input should be validated). Any idea of how to make it work? 🙂

sbreiler commented 5 years ago

@AElmoznino something like this?

let schema = yup
  .object()
  .shape({
    // our "basic" requirements
    emailAddress: yup.string().required().email(), // email will be validated by yup (required)
    red: yup.boolean(),
    orange: yup.boolean(),
    green: yup.boolean()
  })
;

// extended validation
schema = schema.test( // this test is added additional to any other (build-in) tests
  'myCustomCheckboxTest',
  null, // we'll return error message ourself if needed
  (obj) => {
    // only testing the checkboxes here
    if ( obj.red || obj.orange || obj.green ) {
      return true; // everything is fine
    }

    return new yup.ValidationError(
      '❗ Check at least one checkbox',
        null,
        'myCustomFieldName'
      );
    }
);

Here are some tests

ghost commented 5 years ago

Hello, can you help me. I don't understand examples above. I have multiple checkbox inputs, user must select at least one (their choice is what to select) How to display error if they try to submit form without selecting checkbox? I don' know how to check if all fields is empty or valid :(

const ValidateCheckBoxSchema = Yup.object().shape({
  eventOrganizer: Yup.bool().oneOf([true], "Must agree to something"),
  venue: Yup.bool().oneOf([true], "Must agree to something"),
})

....

      <Formik initialValues={{
        eventOrganizer: false,
        venue: false
      }}

        <Field
           type="checkbox"
           name="venue"
          component={CheckBox}
           />
       ....

...
sbreiler commented 5 years ago

If you want to check either one or the other is checked, you should use radio-input.

If you, on the other hand, need to check at least one checkbox is ticked (but could be more than one!), you have to write youre own custom rule for it afaik.

In shape definition only write checkbox as booleans, nothing else:

let ValidateCheckBoxSchema = Yup.object().shape({
    eventOrganizer: Yup.bool(),
    venue: Yup.bool()
});

Extend your ValidateCheckBoxSchema like that (maybe don't use const for it):

// extended validation
ValidateCheckBoxSchema = ValidateCheckBoxSchema.test( // this test is added additional to any other (build-in) tests
  'myCustomCheckboxTest',
  null, // we'll return error message ourself if needed
  (obj) => {
    // only testing the checkboxes here
    if ( obj.eventOrganizer || obj.venue ) { // put every checkbox here
      return true; // everything is fine
    }

    return new yup.ValidationError(
      'Must agree to something', // your custom error message
        null,
        'myCustomFieldName'
      );
    }
);

But don't quote me on that.

ghost commented 5 years ago

Thank you @sbreiler, it works! :) We need to use let. For radio-input is it the same procedure?

sbreiler commented 5 years ago

Radio is much simpler, say you have three radio's like that:

<input type="radio" name="myradio" value="green" />
<input type="radio" name="myradio" value="red" />
<input type="radio" name="myradio" value="blue" />

You validate them like:

Yup.object().shape({
    myradio: Yup.string().oneOf(["green","red","blue"]).required()
});
ghost commented 5 years ago

Thanks again, @sbreiler, you helped me a lot! :)

felipekafuri commented 4 years ago

can someone help me im using typescript and im getting this error FirstTaxBox: "FirstTaxBox must be a boolean type, but the final value was: "on"."

const handleSubmit = useCallback(async (data: object) => {
    try {
      const schema = Yup.object({
        terms: Yup.boolean().oneOf([true], 'At least one check box should be checked'),
      }).shape({
        InvestedVal: Yup.string().required('Should be a value'),
        FirstTaxBox: Yup.boolean(),
        SecondTaxBox: Yup.boolean(),
        ThirdTaxBox: Yup.boolean(),
        FourthTaxBox: Yup.boolean(),
      });

      await schema.validate(data, {
        abortEarly: false,
      });
    } catch (err) {
      const errors = getValidationErrors(err);
      formRef.current?.setErrors(errors);
      console.log(errors);
    }
  }, []);
mevin-g commented 4 years ago

Hi @jquense, One Question - I am using yup with react-hook-form and I need at least 3 checkboxes to be selected from n number of checkboxes that are created dynamically via an API response. Could you please tell me how to do validation for this?

inurlan commented 3 years ago

If we handle checkbox inputs as string array elements my_field: string[] then

yup.object().shape({
   my_field:  yup.array().min(1, "at least one must be selected"),
  });
ynynl commented 3 years ago

This works for me

must select all:

agreementOptions: yup.object({
    agreement1: yup.boolean(),
    agreement2: yup.boolean(),
    agreement3: yup.boolean(),
    agreement4: yup.boolean(),
})
.test(
  'OK', 
  'Please comfirm all of above', 
  a => (a.agreement1 && a.agreement2 && a.agreement3 && a.agreement4) as boolean
 )

must select at least one of them:

languages: yup.object({
    english: yup.boolean(),
    french: yup.boolean(),
    tagalog: yup.boolean(),
    portuguese: yup.boolean(),
    spanish: yup.boolean(),
    chinese: yup.boolean(),
    other: yup.boolean(),
})
.test(
  'OK', 
  'Please select at least one language.', 
  lan => (lan.english||lan.french||lan.tagalog||lan.portuguese||lan.spanish||lan.chinese||lan.other) as boolean
)
markprovenioai commented 3 years ago

How are you able to handle ValidationError? It works well in Fields with formik-material-ui TextField, but is there a way to make it work to a div or label using formik-material-ui? I want to do it in a checkbox question.

wilmerterrero commented 3 years ago

My solution:

initialValues

initialValues={{
     title: "",
     description: "",
     entity: "",
     categories: [],
    }}

Yup object:

categories: Yup.array().test({
      name: "categories_test",
      exclusive: true,
      message: "At least select one",
      test: (value) => value.length > 0,
    })

Fields

<div>
   <Field type="checkbox" name="categories" value="dogs" />
   <Field type="checkbox" name="categories" value="cats" />
   <Field type="checkbox" name="categories" value="rabbits" />
</div>
raufabr commented 3 years ago

Hi, I'm using React Native and I've got the following data structure:

const initialValues = {
    exercises: [
        {
            title: "",
            exerciseType: {
                reps: false,
                duration: false,
                extraWeight: false,
            },
        },
    ],
};

And this is my exercise Schema

const exerciseSchema = yup.object().shape({
    exercises: yup.array().of(
        yup.object().shape({
            title: yup.string().required().min(2).label("Exercise Name"),
            exerciseType: yup.object({
                reps: yup.boolean(),
                duration: yup.boolean(),
            }),
        })
    ),
});

Whilst it's working for title, I can't get it working for the checkboxes. I'm getting the values for reps and duration, but how can I check that at least one of them is true?

codeyourwayup commented 3 years ago

yup.object({ terms: yup .boolean() .oneOf([true], 'Must Accept Terms and Conditions'), });

tariqmurtuza commented 3 years ago

anyone know how i can use yup for multiple checkbox?

Try below validationSchema

const validationSchema = Yup.object({ checked: Yup.array().min(1, 'Select atleast one option of your interest') });

in FormIk use initialValues like below:

<Formik initialValues={{ checked: [] }} />

yuchuga commented 2 years ago

Understand that the following will return true or false for checkbox on console:

yup.object({ terms: yup .boolean() .oneOf([true], 'Must Accept Terms and Conditions'), });

How can I redefine in the schema to change the output on console to '1' if checked or '0' if unchecked for the checkbox. Thanks!

milanjotava commented 2 years ago

If we handle checkbox inputs as string array elements my_field: string[] then

yup.object().shape({
   my_field:  yup.array().min(1, "at least one must be selected"),
  });

only when you give empty array in initial value, Ex, my_field:[]

tharindugithu commented 2 years ago
 const checkboxOptions = [
        { key: 'Option 1', value: 'cOption1' },
        { key: 'Option 2', value: 'cOption2' },
        { key: 'Option 3', value: 'cOption3' }
    ]
  const validationSchema = Yup.object({
       checkboxOption: Yup.array().min(1, 'Required'),
    })
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit}>
           <FormController 
                    control='checkbox' 
                    label='Checkbox topics' 
                    name='checkboxOption' 
                    options={checkboxOptions} />
</Formik>

Must select at least one if not show error ('Required')

CaaioSB commented 1 year ago

Hello, here have two easy ways to do that:

// In this way, you don't need to specific each checkbox, because you are using .mixed().
const yourYupSchema = yup.object().shape({
  yourObject: yup
    .mixed()
    .label("Object of Booleans")
    .test("at-least-one-true", 'At least one selected', (obj) => {
      return Object.values(obj).some((value) => value);
    }),
});

// or

// In this way, you need to specific each checkbox, this looks more secure in a form validation.
const yourYupSchema = yup.object().shape({
  yourObject: yup
    .object({
      termsAccept: yup.boolean(),
      cookieAccept: yup.boolean()
    })
    .label("Object of Booleans")
    .test("at-least-one-true", 'At least one selected', (obj) => {
      return Object.values(obj).some((value) => value);
    }),
});
user4302 commented 1 year ago

this worked for me, i was validating each of these only if the other one did not have an input

step5ValidationSchema: {
//multiple checkboxes, multi select
    statement: Yup.array().when("plan", {
      is: (plan) => {
        return plan === undefined;
      },
      then: (schema) => schema.min(1, "This field is required"),
      otherwise: (schema) => schema.notRequired().optional().nullable(),
    }),
//multiple radio buttons, single select
    plan: Yup.string().when("statement", {
      is: (statement) => {
        return statement.length === 0;
      },
      then: (schema) => schema.required("This field is required"),
      otherwise: (schema) => schema.notRequired().optional().nullable(),
    }),
  },
natecalc commented 1 year ago

How can i do this with multiple check boxes? I want to require both to be ticked, then validate the form. Right now if i tick one then it works. But i need to tick both. Also - not using Yup. Thank you.

OZZlE commented 2 months ago

Using yup + react-hook-form validation what worked for me was this:

const schema = yup
  .object({
    field: yup
      .array()
      .of(yup.boolean().nullable()) // Allow boolean or undefined
      .test(
        "at-least-one-checked",
        "Must select at least one option",
        (value) => !!value?.some((v) => v === true) // Validate if at least one is true
      )
      .required(),
  })
  .required();

    const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(schema),
    mode: "onChange",
    reValidateMode: "onChange",
    defaultValues: {
      field: options.map((option) => props.field.has(option.id)),
    },
  });

  <Controller
    name={`field.${id}`}
    control={control}
    render={({ field: { onChange, ...field } }) => (
      <Checkbox
        {...field}
        checked={props.field.has(field.id)}
        onChange={(event, values) => {
          onChange(values);
          handleChange(event);
        }}
        value={id}
      />
    )}
  />