Hans Poo
Hans Poo

Reputation: 924

Yup validation with two fields related

I'm using formik for form management in reactjs, i have a question on validation with yup.

I have two fields, ones is a select control to select the country, and the other one is a zipcode.

In the country array we have the regex to validate the zipcode, and the idea is to validate the entered zipcode using the regex of the currently selected country, someone can give a clue on how to do this.

Upvotes: 44

Views: 114473

Answers (7)

grumpyTofu
grumpyTofu

Reputation: 1125

An example of a field that requires a numeric value that cannot be higher than the multiplication of two other field's values

const validationSchema = Yup.object().shape({
    num1: Yup.number().positive().required('This field is required.'),
    num2: Yup.number().positive().required('This field is required.'),
    num3: Yup.number().positive().required('This field is required.')
       .when(['num1', 'num2'], (num1, num2, schema) => {
            return num1 > 0 && num2 > 0 ? schema.max(num1 / num2) : schema.max(0);
        })
});

Upvotes: 60

piouson
piouson

Reputation: 4565

You can do conditional validation based on an array of fields. Example below will make field6 required only if at least one of field1 - field5 is not specified.

let schema = object({
  field1: Yup.bool(),
  field2: Yup.bool(),
  field3: Yup.bool(),
  field4: Yup.bool(),
  field5: Yup.bool(),
  field6: Yup.bool().when(
    [
      'field1',
      'field2',
      'field3',
      'field4',
      'field5',
    ],
    {
      is: (...fields) => fields.some(Boolean),
      then: Yup.bool().notRequired(),
      otherwise: Yup.bool().required(),
    }
  ),
});

Upvotes: 1

Aga
Aga

Reputation: 1384

Validation using value of other form field using Yup test().

You can access value of any form field by destructuring parent like const { country } = this.parent;

const yup = require("yup");

const schema = yup.object({
  country: yup.object().required("This field is required"),
  zipcode: yup
    .string()
    .required("This field is required")
    .test("is-right-zipcode", "Invalid zipcode", function(code) {
      const { country } = this.parent;
      return code.match(country.regex);
    })
});

Upvotes: 11

agm1984
agm1984

Reputation: 17150

Here's an example of enforcing a datepicker's end date being after the start date:

const schema = Yup.object().shape({
    start_date: Yup.date()
        .typeError('Start Date is required')
        .required('Start Date is required'),
    end_date: Yup.date()
        .typeError('End Date is required')
        .required('End Date is required')
        .when('start_date', (start_date) => {
            if (start_date) {
                return Yup.date()
                    .min(start_date, 'End Date must be after Start Date')
                    .typeError('End Date is required')
            }
        }),
})

It was required to put the if statement to validate correctly while the form was loading, and it was also required to put the typeError rule in to validate correctly when the start date was selected but the end date wasn't yet.

The important thing is you can see usage of when; the first param is the field to check against, and the second param is the validation function which returns a Yup validation object.

I tried to just return true or false, but it seemed to throw errors, so it's a bit more complex than it just being a pure validation function.

Upvotes: 14

Amiratak88
Amiratak88

Reputation: 1529

I had the exact same problem. I was a able to use when to solve it.

import { object, string } from 'yup';

const validCountries = ['US', 'CA'];

const zipRegexes = {
    US: /^\d{5}(?:-?\d{4})?$/,
    CA: /^[ABCEGHJKLMNPRSTVXY]\d[A-Z]\d[A-Z]\d$/
};

const addressSchema = object().shape({
    country: string()
        .oneOf(validCountries, 'Please select a country from the list above')
        .required('Please select a country from the list above'),
    /* Other fields
    .
    .
    .
    */
    zip: string()
        .trim()
        .required('Required')
        .transform(value => value.toUpperCase())
        .when('country', (country, schema) => {
            if (
                string()
                    .oneOf(validCountries)
                    .required()
                    .isValid(country)
            ) {
                return schema.matches(
                    zipRegexes[country],
                    `not a valid ${country} zip code`
                );
            }

            return schema;
        })
});

Upvotes: 7

Vimal Sharma
Vimal Sharma

Reputation: 67

let schema = object({
  isBig: boolean(),
  count: number()
    .when('isBig', {
      is: true, // alternatively: (val) => val == true
      then: yup.number().min(5),
      otherwise: yup.number().min(0),
    })
});

Upvotes: 4

Tamlyn
Tamlyn

Reputation: 23572

Use .when() and pass it a function which returns the schema for the ZIP code based on the value of the country.

Upvotes: 6

Related Questions