Yup: deep validation in array of objects
Asked Answered
H

6

58

I have a data structure like this:

{
  "subject": "Ah yeah",
  "description": "Jeg siger...",
  "daysOfWeek": [
    {
      "dayOfWeek": "MONDAY",
      "checked": false
    },
    {
      "dayOfWeek": "TUESDAY",
      "checked": false
    },
    {
      "dayOfWeek": "WEDNESDAY",
      "checked": true
    },
    {
      "dayOfWeek": "THURSDAY",
      "checked": false
    },
    {
      "dayOfWeek": "FRIDAY",
      "checked": false
    },
    {
      "dayOfWeek": "SATURDAY",
      "checked": true
    },
    {
      "dayOfWeek": "SUNDAY",
      "checked": true
    }
  ],
  "uuid": "da8f56a2-625f-400d-800d-c975bead0cff",
  "taskSchedules": [],
  "isInitial": false,
  "hasChanged": false
}

In daysOfWeek I want to ensure that at least one of the items has checked: true.

This is my validation schema so far (but not working):

const taskValidationSchema = Yup.object().shape({
  subject: Yup.string().required('Required'),
  description: Yup.string(),
  daysOfWeek: Yup.array()
    .of(
      Yup.object().shape({
        dayOfWeek: Yup.string(),
        checked: Yup.boolean(),
      })
    )
    .required('Required'),
  taskSchedules: Yup.array(),
})

Is it possible to validate the values of daysOfWeek ensuring that at least one of them has checked: true?

Hawker answered 5/12, 2019 at 14:41 Comment(0)
H
62

I solved it using compact() (filtering out falsely values) together with setTimeout after the FieldArray modifier function:

const validationSchema = Yup.object().shape({
  subject: Yup.string().required(i18n.t('required-field')),
  description: Yup.string(),
  daysOfWeek: Yup.array()
    .of(
      Yup.object().shape({
        dayOfWeek: Yup.string(),
        checked: Yup.boolean(),
      })
    )
    .compact((v) => !v.checked)
    .required(i18n.t('required-field')),
  taskSchedules: Yup.array(),
});

And in form:

<Checkbox
  value={day.dayOfWeek}
  checked={day.checked}
  onChange={(e) => {
    replace(idx, { ...day, checked: !day.checked });
    setTimeout(() => {
      validateForm();
    });
  }}
/>;
Hawker answered 6/12, 2019 at 10:19 Comment(3)
The setTimeout thing just made my day :-) There was a really strange behaviour when using arrayHelper. Thanks a lot.Itemized
How can I reference to (e.g.) description from a when() condition in (e.g.) dayOfWeek? Basically... dayOfWeek: Yup.string().when("description", {is: ..., then: ...} but it doesn't work from inside the array.Honeycomb
If you use compact() the form data is also filtered. that means in the form data after validation the array will only contain only truthy values. the solution is to use test() for custom validation. test('atLeastOne', 'error message', days => days.some(day => day.checked))Sidky
C
16

Base on @olefrank's answer. This code work with me.

const validationSchema = Yup.object().shape({
  subject: Yup.string().required(i18n.t('required-field')),
  description: Yup.string(),
  daysOfWeek: Yup.array()
    .of(
      Yup.object().shape({
        dayOfWeek: Yup.string(),
        checked: Yup.boolean(),
      })
    )
    .compact((v) => !v.checked)
    .min(1, i18n.t('required-field')), // <– `.min(1)` instead of `.required()`
  taskSchedules: Yup.array(),
});
Codification answered 7/2, 2022 at 3:47 Comment(0)
G
6

I have done this type of validation in my Node.js(Express.js) project. You can try validation in this way.

const validationSchema = yup.object({
  subject: yup.string().required(),
  description: yup.string().required(), 
  daysOfWeek: yup.array(
    yup.object({
      dayOfWeek: yup.string().required(),
      checked: yup.boolean().required()
    })
  )
})
Gainsborough answered 21/3, 2022 at 9:10 Comment(0)
P
3

If you trying in latest version it should be used like this Yup.array().min(1, "At least one option is required").required()

Pelt answered 17/6, 2022 at 16:17 Comment(0)
C
2

In my case I have used formik with yup for this,

I want to select only one value in an array if not selected I need to display the error

array =  [{"label": "Option 1", "selected": false, "value": "option-1"}, {"label": "Option 2", "selected": false, "value": "option-2"}, {"label": "Option 3", "selected": false, "value": "option-3"}]

Yup.mixed().test({
 message: 'Required',test: 
val => val.filter(i => i.selected === true).length === 1})

it worked for me

Yup.mixed().test({
 message: 'Required',test: 
val => val.filter(i => i.selected === true).length !== 0})
Callisthenics answered 10/8, 2022 at 10:23 Comment(1)
Is this describing an problem/error your encounter and want help with? Or is this saying thanks for a proposed solution in an existing answer here? Or should you edit this according to How to Answer and stackoverflow.com/editing-help in order to make more obvious that you are trying to contribute a solution?Virgil
D
0

I did a similar validation like this. You can try this approach

const schema = yup.object().shape({
    subject: yup.string().required(),
    description: yup.string()
    daysOfWeek: yup.array().of(
      yup.lazy(value => {
        const { checked } = value // Get the value of checked field

        return checked
          ? yup.object().shape({
              dayOfWeek: yup.string().required(), // validate only if checked is true
              checked: yup.boolean()
            }) 
           : yup.object().shape({
              dayOfWeek: yup.string(),
              checked: yup.boolean()
            })
      })
    ),
    taskSchedules: yup.array()
})
Dangerfield answered 29/4, 2023 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.