Published on

Formik Yup Validation: Require One of Two Fields

Authors
  • Name
    Ripal & Zalak
    Twitter

Validating One of Two Fields in Formik Using Yup

When working with dynamic forms, you may encounter a situation where you need to ensure that at least one of two fields is filled. This is common in cases where multiple options are presented to users, and only one is required to proceed. In this guide, we will tackle this problem using Formik and Yup with a practical example.

Example Scenario

Imagine you have a job application form with the following fields:

  • Portfolio URL: A string field where applicants can share a link to their portfolio.
  • Uploaded Resume: An array field where applicants can upload one or more files.

You want to enforce a rule where applicants must provide either a portfolio URL or upload a resume, but they do not need to provide both. This ensures flexibility while maintaining required input for the application process.

The Solution

To implement this logic, we use Yup's when method to create conditional validation rules. Here is the schema:

import * as Yup from 'yup'

const applicationSchema = Yup.object().shape(
  {
    name: Yup.string().required('Name is required.'),
    email: Yup.string().email('Invalid email format').required('Email is required.'),
    portfolioUrl: Yup.string()
      .url('Must be a valid URL')
      .when('resumeFiles', {
        is: (resumeFiles) => !resumeFiles || resumeFiles.length === 0,
        then: Yup.string().required('Either portfolio URL or resume is required.'),
        otherwise: Yup.string(),
      }),
    resumeFiles: Yup.array()
      .of(Yup.mixed().required('File is required.'))
      .when('portfolioUrl', {
        is: (portfolioUrl) => !portfolioUrl || portfolioUrl.trim().length === 0,
        then: Yup.array().min(
          1,
          'At least one resume file is required if no portfolio URL is provided.'
        ),
        otherwise: Yup.array(),
      }),
  },
  [['portfolioUrl', 'resumeFiles']] // Prevent cyclic dependency
)

Key Features of the Schema

  1. Conditional Validation with when: Each field's requirement depends on whether the other is empty.
  2. User-Friendly Errors: Clear error messages guide users to fix their inputs.
  3. Cyclic Dependency Prevention: Passing an array of field pairs avoids validation loops.

Full Implementation

Below is how this schema integrates into a Formik form:

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

const initialValues = {
  name: '',
  email: '',
  portfolioUrl: '',
  resumeFiles: [],
}

const applicationSchema = Yup.object().shape(
  {
    name: Yup.string().required('Name is required.'),
    email: Yup.string().email('Invalid email format').required('Email is required.'),
    portfolioUrl: Yup.string()
      .url('Must be a valid URL')
      .when('resumeFiles', {
        is: (resumeFiles) => !resumeFiles || resumeFiles.length === 0,
        then: Yup.string().required('Either portfolio URL or resume is required.'),
        otherwise: Yup.string(),
      }),
    resumeFiles: Yup.array()
      .of(Yup.mixed().required('File is required.'))
      .when('portfolioUrl', {
        is: (portfolioUrl) => !portfolioUrl || portfolioUrl.trim().length === 0,
        then: Yup.array().min(
          1,
          'At least one resume file is required if no portfolio URL is provided.'
        ),
        otherwise: Yup.array(),
      }),
  },
  [['portfolioUrl', 'resumeFiles']]
)

const JobApplicationForm = () => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={applicationSchema}
      onSubmit={(values) => {
        console.log('Form Submitted', values)
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <div>
            <label>Name</label>
            <Field name="name" type="text" />
            <ErrorMessage name="name" component="div" />
          </div>

          <div>
            <label>Email</label>
            <Field name="email" type="email" />
            <ErrorMessage name="email" component="div" />
          </div>

          <div>
            <label>Portfolio URL</label>
            <Field name="portfolioUrl" type="text" />
            <ErrorMessage name="portfolioUrl" component="div" />
          </div>

          <div>
            <label>Resume Files</label>
            <Field name="resumeFiles" as="textarea" />
            <ErrorMessage name="resumeFiles" component="div" />
          </div>

          <button type="submit" disabled={isSubmitting}>
            Submit Application
          </button>
        </Form>
      )}
    </Formik>
  )
}

export default JobApplicationForm

FAQs

1. Can I validate more complex relationships between fields? Yes, Yup's test method provides more granular control for custom validation logic.

2. What happens if both fields are filled? This schema allows both fields to be filled, enforcing only that at least one must be.

3. Why are there cyclic dependency errors? Cyclic dependency occurs when Yup cannot determine validation order. Using the second argument in Yup.object().shape resolves this.