Why is YUP conditional validation causing branch is not a function at Condition.fn error
Asked Answered
T

7

19

I have a MUIRadioGroup (options = Low, Medium and High) and a multi-select MUIAutocomplete component on my form.

<MUIRadioGroup
              name="requestDetail.riskAssesmentDetail.riskClassification"
              label="Risk Classification"
              variant="outlined"
              size="small"
              defaultValue
            />

<MUIAutocomplete
                  name="requestDetail.riskAssesmentDetail.safetyDetail.investigationType"
                  label="Type of Investigation"
                  variant="outlined"
                  size="small"
                  multiple
                  disableCloseOnSelect
                  options={API_Safety_InvestigationType}
                />

My Schema is...

requestDetail: yup.object().shape({ ...
    riskAssesmentDetail: yup.object().shape({
      riskClassification: yup
        .string()
        .label("Risk Classification")
        .required()
        .typeError("Invalid Selection"),
      safetyDetail: yup
        .object()
        .shape()
        .when("riskClassification", {
          is: (riskClassification) =>
            ["Medium", "High"].includes(riskClassification),
          then: yup.object({
            investigationType: yup
              .array()
              .label("Type of Investigation")
              .min(1),
          }),
        }),
    }),

When radio button is "Medium" or "High, I need to perform validation on the MUIAutocomplete component.

I get an error...

Uncaught (in promise) TypeError: branch is not a function
    at Condition.fn (index.esm.js:175:1)
    at Condition.resolve (index.esm.js:188:1)
    at index.esm.js:586:1
    at Array.reduce (<anonymous>)
    at ObjectSchema.resolve (index.esm.js:586:1)
    at ObjectSchema._cast (index.esm.js:1670:1)
    at ObjectSchema.cast (index.esm.js:610:1)
    at ObjectSchema._cast (index.esm.js:1683:1)
    at ObjectSchema.cast (index.esm.js:610:1)
    at ObjectSchema._cast (index.esm.js:1683:1)

If I remove the conditional validation all works...

requestDetail: yup.object().shape({ ...

    riskAssesmentDetail: yup.object().shape({
      riskClassification: yup.string().label("Risk Classification").required(),
      safetyDetail: yup
        .object()
        .shape({
          investigationType: yup.array().label("Type of Investigation").min(1),
        }),
    }),

What is this error about and how do I fix it?

Transformer answered 24/2, 2023 at 7:38 Comment(3)
There seems to be a link to YUP that deals with V1.0.0 covering some changes,but I'm clueless. on how to change my code to suit...Transformer
found at github.com/jquense/…Transformer
referenced through github.com/jquense/yup/issues/1918Transformer
H
41

I just ran into this issue as well. Your when validation needs to change to have a function on the then property.

      safetyDetail: yup
        .object()
        .shape()
        .when("riskClassification", {
          is: (riskClassification) =>
            ["Medium", "High"].includes(riskClassification),
          then: () => yup.object({
            investigationType: yup
              .array()
              .label("Type of Investigation")
              .min(1),
          }),
        }),
Hangbird answered 24/2, 2023 at 19:38 Comment(2)
Incase it didn't work for you, If you have an otherwise: be sure to change that as well to have a functionTeeming
Yes, it worked. ThanksHillell
P
19

I ran into the same issue while doing upgrades. The old version of yup accepted the following.

someField: string().when("someOtherField", {
      is: true,
      then: string()
        .max(5, "Must be 5 characters or less")
        .required("Required")
    })

The above code was giving me a TypeScript error after upgrading yup. I solved it using the following

someField: string().when("someOtherField", {
      is: true,
      then: () => string()
        .max(5, "Must be 5 characters or less")
        .required("Required")
    })
Persecution answered 29/3, 2023 at 14:11 Comment(2)
Thank you! I needed to add the anonymous function at the start of my .then() as well.Agonized
Thanks you for inputs it works in typescriptPointenoire
P
3

When validation now has a new syntax:

let schema = object({
id: yup.number().notRequired(),
thumbnail: yup.mixed().when('id', {
  is: (id: number | undefined) => id !== undefined,
  then: (schema) => schema.required(),
  otherwise: (schema) => schema.notRequired(),
});

Notice the then: and otherwise: are functions with schema as a parameter. More examples from the documentation

Pail answered 11/6, 2023 at 13:30 Comment(0)
A
2

for those who want a real and complex example : Here is before upgrade version :

import * as Yup from 'yup';

interface optionsType {
  id: string;
  label: string;
  value: string;
}

const formEpischema = Yup.object().shape(
  {
    attestationType: Yup.object({}).shape({
      id: Yup.string().required(),
      label: Yup.string().required(),
      value: Yup.string().required()
    }),
    activityType: Yup.object({}).when('attestationType', {
      is: (attestationType: optionsType) =>
        attestationType.label !== 'Standard',
      then: Yup.object({}).shape({
        id: Yup.string().required(),
        label: Yup.string().required(),
        value: Yup.string().required()
      }),
      otherwise: Yup.object({
        id: Yup.string(),
        label: Yup.string(),
        value: Yup.string()
      })
    }),
    shoesType: Yup.array().when(['helmetType', 'otherEquipement'], {
      is: (helmetType: optionsType[], otherEquipement: string) =>
        !helmetType?.length && !otherEquipement,
      then: Yup.array()
        .of(
          Yup.object().shape({
            id: Yup.string().required(),
            label: Yup.string().required(),
            value: Yup.string().required()
          })
        )
        .min(1)
        .required()
    }),
    shoesCriteria: Yup.object({}).shape({
      id: Yup.string(),
      label: Yup.string(),
      value: Yup.string()
    }),
    shoesSize: Yup.number().min(10).max(55),
    helmetType: Yup.array().when(['shoesType', 'otherEquipement'], {
      is: (shoesType: optionsType[], otherEquipement: string) =>
        !shoesType?.length && !otherEquipement,
      then: Yup.array()
        .of(
          Yup.object().shape({
            id: Yup.string().required(),
            label: Yup.string().required(),
            value: Yup.string().required()
          })
        )
        .min(1)
        .required()
    }),
    otherEquipement: Yup.string()
      .min(4)
      .when(['shoesType', 'helmetType'], {
        is: (shoesType: optionsType[], helmetType: optionsType[]) =>
          !shoesType?.length && !helmetType?.length,
        then: Yup.string().min(4).required()
      }),
    isCefri: Yup.boolean().oneOf([true, false]),
    dosimeterType: Yup.object({}).when('isCefri', {
      is: true,
      then: Yup.object({}).shape({
        id: Yup.string().required(),
        label: Yup.string().required(),
        value: Yup.string().required()
      }),
      otherwise: Yup.object({
        id: Yup.string(),
        label: Yup.string(),
        value: Yup.string()
      })
    }),
    dosimeterRef: Yup.string().when('isCefri', {
      is: true,
      then: Yup.string().min(7).max(7).required(),
      otherwise: Yup.string()
    })
  },
  [
    ['shoesType', 'helmetType'],
    ['shoesType', 'otherEquipement'],
    ['helmetType', 'otherEquipement']
  ]
);

export default formEpischema;


Here is now :

import * as Yup from 'yup';

interface optionsType {
  id: string;
  label: string;
  value: string;
}

const formEpischema = Yup.object().shape({
  attestationType: Yup.object({ // change is here 👈
    id: Yup.string().required(),
    label: Yup.string().required(),
    value: Yup.string().required()
  }),

  activityType: Yup.object().when('attestationType', {
    is: (attestationType: optionsType) => attestationType.label !== 'Standard',
    then: () => // change is here 👈
      Yup.object({
        id: Yup.string().required(),
        label: Yup.string().required(),
        value: Yup.string().required()
      }),
    otherwise: () => // change is here 👈
      Yup.object({
        id: Yup.string(),
        label: Yup.string(),
        value: Yup.string()
      })
  }),

  shoesType: Yup.array().when(['helmetType', 'otherEquipement'], {
    is: (helmetType: optionsType[], otherEquipement: string) => !helmetType?.length && !otherEquipement,
    then: () => // change is here 👈
      Yup.array()
        .of(
          Yup.object().shape({
            id: Yup.string().required(),
            label: Yup.string().required(),
            value: Yup.string().required()
          })
        )
        .min(1)
        .required()
  }),

  shoesCriteria: Yup.object({
    id: Yup.string(),
    label: Yup.string(),
    value: Yup.string()
  }),

  shoesSize: Yup.number().min(10).max(55),

  helmetType: Yup.array().when(['shoesType', 'otherEquipement'], {
    is: (shoesType: optionsType[], otherEquipement: string) => !shoesType?.length && !otherEquipement,
    then: () =>
      Yup.array()
        .of(
          Yup.object().shape({
            id: Yup.string().required(),
            label: Yup.string().required(),
            value: Yup.string().required()
          })
        )
        .min(1)
        .required()
  }),

  otherEquipement: Yup.string().min(4).when(['shoesType', 'helmetType'], {
    is: (shoesType: optionsType[], helmetType: optionsType[]) => !shoesType?.length && !helmetType?.length,
    then: () => Yup.string().min(4).required()
  }),

  isCefri: Yup.boolean().oneOf([true, false]),

  dosimeterType: Yup.object().when('isCefri', {
    is: true,
    then: () =>
      Yup.object({
        id: Yup.string().required(),
        label: Yup.string().required(),
        value: Yup.string().required()
      }),
    otherwise: () =>
      Yup.object({
        id: Yup.string(),
        label: Yup.string(),
        value: Yup.string()
      })
  }),

  dosimeterRef: Yup.string().when('isCefri', {
    is: true,
    then: (schema) => schema.min(7).max(7).required(),
    otherwise: () => Yup.string()
  })
    },
  [
    ['shoesType', 'helmetType'],
    ['shoesType', 'otherEquipement'],
    ['helmetType', 'otherEquipement']
  ]
);

export default formEpischema;


Old version (0.3): The is, then, and otherwise functions of conditional validations accepted objects as arguments. New version (1): The then and otherwise functions must return a modified schema. They now take empty arrow functions () => ... to return the modified schema.

Old version (0.3): Validation methods such as required(), min(), max(), etc. were called directly on the fields (for example: Yup.string().required()). New version (1): Validation methods must be called via the schema (for example: Yup.string().required() becomes Yup.string().required()).

Old version (0.3): The mixed() method was used to create a schema when the type was not specified. New version (1): The mixed() method has been removed. You must specify the type directly when creating the schema (for example: Yup.string()).

Old version (0.3): The validate() method directly returned validation errors if they existed, otherwise returned undefined. New version (1): The validate() method returns a promise. You must use await or .then() to get the results.

Old version (0.3): The isValid() method directly returned true if the validation was successful, otherwise returned false. New version (1): The isValid() method returns a promise. You must use await or .then() to get the results.

Old version (0.3): The nullable() and notRequired() methods were used to specify that a field could be null or not required. New version (1): These methods have been removed. To allow a field to be null or not required, you can simply not call any validation method after the field (eg: Yup.string().nullable() just becomes Yup.string()).

New version (1): The oneOf() method is now used to validate that a value is part of a given set. For example: Yup.string().oneOf(['value1', 'value2']). These changes reflect some of the important differences between the two versions of Yup.

Auriferous answered 31/7, 2023 at 15:52 Comment(0)
P
1

I too was facing same problem where I used Yup conditional validation schema in Javascript with when condition,

Like this // JAVASCRIPT VERSION

    export const CreateTicketValidationSchema = Yup.object({

  // Extra validation for courtesy call making
  logCourtesyCall: Yup.boolean(),
  customer: Yup.string().when("logCourtesyCall", {
    is: (value) => value === false,
    then: Yup.string().required("Field is required").max(250, "Maximum character limit exceed"),
    otherwise: Yup.string().max(250, "Maximum character limit exceed"),
  }),
  accountManager: Yup.string().when("logCourtesyCall", {
    is: (value) => value === false,
    then: Yup.string().required("Field is required").max(50, "Maximum character limit exceed"),
    otherwise: Yup.string().max(50, "Maximum character limit exceed"),
  }),
 
});

// UPDATED CODE IF YOU ARE FACING ISSUE IN TYPESCRIPT VERSION OF YUP

  export const forgetPasswordValidation = Yup.object().shape({
  emailStatus: Yup.bool(),
  otpStatus: Yup.bool(),

  email: Yup.string().when('emailStatus', {
    is: false,
    then: () =>
      Yup.string().email('Invalid email address').required('Please enter email address'),
  }),

  otp: Yup.string().when('emailStatus', {
    is: true,
    then: () => Yup.string().min(6).max(6).required('Please enter OTP'),
  }),
  newPassword: Yup.string().when('otpStatus', {
    is: true,
    then: () =>
      Yup.string()
        .required('New password is required')
        .min(8, 'Password must be at least 8 characters long')
        .matches(
          /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
          'Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character',
        ),
  }),

  confirmPassword: Yup.string().when('otpStatus', {
    is: true,
    then: () =>
      Yup.string().oneOf([Yup.ref('newPassword')], 'Both password need to be the same'),
  }),
});

Hope this will help you. :)

Pointenoire answered 14/6, 2023 at 14:53 Comment(0)
D
0

I changed the otherwise into a function and it did work.

const signInSchema = Yup.object().shape({
    loginType: Yup.string().required("Type is required.").notOneOf(['Select'], 'Type is required.'),
    shortName: Yup.string().when("loginType", {
        is: (otherFieldValue) => otherFieldValue === "2",
        then: Yup.string().required(
            "Short Name is required."),
        otherwise: () => Yup.string(),
    }),
    email: Yup.string().email("Email must be a valid email.").required("Email is required"),
    password: Yup.string().required('Password is required.')
        .min(8, 'Password is too short - should be 8 chars minimum.')
})
Deedeeann answered 4/11, 2023 at 12:12 Comment(0)
R
0

Here is real example with multi schema validation. Be carefully then and otherwise are functions

const educationInfoSchema = yup.object().shape({
  educationDocumentNumber: yup
    .string()
    .required("Education document number is required"),
  faculty: yup.string().required("Faculty is required"),
  specialty: yup.string().required("Specialty is required"),
});

const degreeInfoSchema = yup.object().shape({
  scientificDegreeName: yup
     .string()
     .required("Scientific degree name is required"),
  issuingInstitution: yup.string().required("Issuing institution is required"),
});

const mainFormSchema = yup.object().shape({
  educationInfos: yup
    .array()
    .of(educationInfoSchema)
    .min(1, "At least one education info is required"),
  hasDegree: yup.boolean().required(),
  degreeInfos: yup.array().when("hasDegree", {
    is: true,
    then: () =>
      yup
        .array()
        .of(degreeInfoSchema)
        .min(1, "At least one degree info is required"),
    otherwise: () => yup.array().notRequired(),
  }),
});
Radmilla answered 7/8, 2024 at 13:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.