34

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.

Hans Poo
  • 804
  • 1
  • 8
  • 17
  • Hope this answer to another similar question can help: https://stackoverflow.com/questions/49394391/conditional-validation-in-yup/56861567#56861567 – Eric Tan Jul 03 '19 at 02:50

7 Answers7

44

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);
        })
});
Yatrix
  • 13,361
  • 16
  • 48
  • 78
grumpyTofu
  • 826
  • 7
  • 5
10

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.

agm1984
  • 15,500
  • 6
  • 89
  • 113
8

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);
    })
});
Aga
  • 1,019
  • 1
  • 11
  • 16
  • This worked for my case where I needed to compare the current field against the value of another field. Trying to use the current fields value in a `when` resulted in a circular reference error. – quicklikerabbit Nov 25 '22 at 21:04
6

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;
        })
});
Amiratak88
  • 1,204
  • 12
  • 18
5

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

Tamlyn
  • 22,122
  • 12
  • 111
  • 127
5
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),
    })
});
Vimal Sharma
  • 69
  • 1
  • 1
  • 3
    Try, when answering, write code with its description – S.Daineko May 18 '20 at 16:08
  • (val) => val == true I was not aware of this option. This helped as I was stuck with Select field validation. – DpkTheJavaCoder Feb 26 '21 at 14:59
  • This is taken directly from the yup documentation. Without any attribution that's pretty tacky. https://github.com/jquense/yup/tree/pre-v1#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema – flyingace May 12 '22 at 14:16
1

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(),
    }
  ),
});
piouson
  • 3,328
  • 3
  • 29
  • 29
  • Any idea how this might look for a dynamic array? I have a use case where the number of items in an array is not known, but the sum of their quantities can not be higher than a number (also dynamic based on data) – Crhistian Ramirez Jul 05 '23 at 01:21