Published on

Deep Validation in Array of Objects with Yup and Formik

Authors
  • Name
    Ripal & Zalak
    Twitter

Deep Validation in Array of Objects with Yup and Formik

When working with complex forms in React, especially those containing arrays of objects, you may encounter scenarios where you need to validate that at least one item in an array meets specific criteria. In this blog, we’ll explore how to achieve this using Yup with Formik.

The Problem

Imagine a form structure like this:

{
  "subject": "Task Title",
  "description": "Task Description",
  "daysOfWeek": [
    { "dayOfWeek": "MONDAY", "checked": false },
    { "dayOfWeek": "TUESDAY", "checked": false },
    { "dayOfWeek": "WEDNESDAY", "checked": true },
    { "dayOfWeek": "THURSDAY", "checked": false },
    { "dayOfWeek": "FRIDAY", "checked": false }
  ],
  "uuid": "unique-id"
}

You want to ensure at least one daysOfWeek object has checked: true.

Solution with Yup

To validate this, we use Yup’s .test method. Here's the validation schema:

import * as Yup from 'yup'

const validationSchema = Yup.object().shape({
  subject: Yup.string().required('Subject is required'),
  description: Yup.string(),
  daysOfWeek: Yup.array()
    .of(
      Yup.object().shape({
        dayOfWeek: Yup.string().required('Day is required'),
        checked: Yup.boolean(),
      })
    )
    .test('at-least-one-checked', 'At least one day must be checked', (days) => {
      return days.some((day) => day.checked)
    }),
  uuid: Yup.string().required('UUID is required'),
})

Explanation

  • Schema Definition:
    • daysOfWeek is an array where each item must adhere to its own schema.
    • The .test method ensures at least one checked field is true.

Form Implementation with Formik

Here’s how you can integrate this schema with a Formik form:

import React from 'react'
import { Formik, Field, Form } from 'formik'
import * as Yup from 'yup'

const initialValues = {
  subject: '',
  description: '',
  daysOfWeek: [
    { dayOfWeek: 'MONDAY', checked: false },
    { dayOfWeek: 'TUESDAY', checked: false },
    { dayOfWeek: 'WEDNESDAY', checked: false },
  ],
  uuid: '',
}

const validationSchema = Yup.object().shape({
  // (Use the schema defined above)
})

const MyForm = () => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={(values) => {
        console.log('Form Submitted:', values)
      }}
    >
      {({ values, errors, touched }) => (
        <Form>
          <div>
            <label>Subject:</label>
            <Field name="subject" type="text" />
            {errors.subject && touched.subject && <div>{errors.subject}</div>}
          </div>
          <div>
            <label>Description:</label>
            <Field name="description" type="text" />
          </div>
          <div>
            <label>Days of Week:</label>
            {values.daysOfWeek.map((day, index) => (
              <div key={index}>
                <Field name={`daysOfWeek[${index}].checked`} type="checkbox" />
                {day.dayOfWeek}
              </div>
            ))}
            {errors.daysOfWeek && <div>{errors.daysOfWeek}</div>}
          </div>
          <div>
            <label>UUID:</label>
            <Field name="uuid" type="text" />
            {errors.uuid && touched.uuid && <div>{errors.uuid}</div>}
          </div>
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  )
}

export default MyForm

FAQs

1. Why use .test instead of .min?

.min validates the array length, but .test allows custom logic, such as ensuring one checked field is true.

2. Can this be extended to multiple conditions?

Yes! Modify the .test function to include additional conditions as needed.

3. What if no days are selected?

The error message defined in .test will trigger, ensuring proper user feedback.