Formik: Per schema conditional validation

Created on 9 Jul 2019  路  4Comments  路  Source: formium/formik

I'm trying to build a form with Formik but I'm unable to do the type of conditional validation that I need for my schemas. I have a Formik form where I'm trying to fill out an object called profile. The object looks something like this, this is what I have in my Formik initialValues:

const profile = {
    profile_first_name: undefined,
    profile_last_name: undefined,
    profile_time_zone: undefined,
    profile_company_name: undefined,
    profile_work_histories: [
        profile_work_histories
    ],
    profile_educations: [
        profile_educations
    ]
};

Here is how my schema looks like:

export const ClientCreateAccountSchema = Yup.object().shape({
    profile: Yup.object().shape({
        profile_first_name: Yup.string().required('Cannot be empty'),
        profile_last_name: Yup.string().required('Cannot be empty'),
        profile_time_zone: Yup.string().required('Cannot be empty').length(3, 'Must be 3 characters (ex.PST)'),
        profile_company_name: Yup.string().required('Cannot be empty'),
        profile_work_histories: profileWorkHistory,
        profile_educations: profileEducationSchema
    })
});

export const profileEducationSchema = Yup.array().of(Yup.object().shape({
    profile_education_school_name: Yup.string().required(),
    profile_education_degree_type: Yup.string().required(),
    profile_education_degree_major: Yup.string().required(),
    profile_education_school_country: Yup.string().required(),
    profile_education_school_province: Yup.string().required(),
    profile_education_start_date: Yup.string().required(),
    profile_education_end_date: Yup.string().matches(/^((0[1-9])|(1[0-2]))-[0-9]{4}$/, 'Must be in MM-YYYY format')
}));

export const profileWorkHistory = Yup.array().of(Yup.object().shape({
    profile_work_history_company_name: Yup.string().required(),
    profile_work_history_employment_url: Yup.string().url(),
    profile_work_history_number_of_attorneys: Yup.number('Must be a number').required(),
    profile_work_history_company_country: Yup.string().required(),
    profile_work_history_company_province: Yup.string().required(),
    profile_work_history_company_city: Yup.string().required(),
    profile_work_history_position: Yup.string().required(),
    profile_work_history_employment_start_date: Yup.string().required(),
    profile_work_history_employment_end_date: Yup.string().required(),
    profile_work_history_is_current: Yup.bool().required()
}));

Keep in mind that I also have 4 other subschemas at that level, but I removed them for simplicity. The other subschemas are called profileBarAssociationsSchema, profileJurisdictionSchema, profileLanguagesSchema, profileIndustrySchema. They all have up to 6 fields that need to be filed out.

So far this is what I've tried:

  • I wrote a function called requiredIf():
const requiredIf = (parent, dependent) => {
    let keys = parent && Object.keys(parent);
    let allValuesExist = keys.filter(field => parent && parent[field]).length !== 0;

    return !allValuesExist || parent && parent[dependent] && parent[dependent] !== '';
};
  • And its called inside a test for each field that I want to have validated:
export const profileEducationSchema = Yup.array().of(Yup.object().shape({
    profile_education_school_name: Yup.string()
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_school_name');
        }),
    profile_education_degree_type: Yup.string()
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_degree_type');
        }),
    profile_education_degree_major: Yup.string()
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_degree_major');
        }),
    profile_education_school_country: Yup.string()
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_school_country');
        }),
    profile_education_school_province: Yup.string()
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_school_province');
        }),
    profile_education_start_date: Yup.string()
        .test('matches-If','Must be in MM-YYYY format', function (value) {
            return value == null || value === '' ? true : /^((0[1-9])|(1[0-2]))-[0-9]{4}$/.test(value);
        })
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_start_date');
        }),
    profile_education_end_date: Yup.string().matches(/^((0[1-9])|(1[0-2]))-[0-9]{4}$/, 'Must be in MM-YYYY format')
        .test('required-If', 'Cannot be empty', function () {
            return requiredIf(this.parent, 'profile_education_end_date');
        })
}));

But I get an error everything I tried to add a new field to the FieldArray and start filling out the second item instead of the first one.

Another thing I've tried was to use Yup.when but I have so many fields that it is hard to keep track of all the conditional validation combinations, that makes it almost impossible .

My question is how can I configure my ClientCreateAccountSchema so that the sub schemas are validated separately from each other? In other words, how can I my schema only validate the respective subchema when touched and not activate the other subschema? Is this something I can do with Yup? or is this something with Formik?

What other suggestions do you guys have?

Formik Question stale

Most helpful comment

@jaredpalmer On another note I think validationSchema = { values => { does not pass through the values . validate = { values => { will do though.

All 4 comments

A few options:

  • You might be able to just use the function form of validationSchema to return the ideal schema based on the current values rather than trying to fit it all into one schema.
    jsx <Formik validationSchema={values => { let schemaShape = {} if (values.thing) { return Yup.object().shape({ thing: Yup.string().required() profileOtherThing: Yup.string().required() }) } // otherwise do something else return Yup.object().shape({/* ... */}) }}/>
  • If you need to do specialized validation at a path, then you could try to use a custom validate function that uses Yup and perhaps Yup.schema.validateAt() + some biz logic. You will likely want to utilize this helper method too https://github.com/jaredpalmer/formik/blob/master/src/Formik.tsx#L890

Yup has conditional validation using 'when' and passing through the context . here. I'm also unsure how to take advantage of this within formik. see my question earlier here

@jaredpalmer On another note I think validationSchema = { values => { does not pass through the values . validate = { values => { will do though.

Is it possible to separate the Form into smaller forms and submit them all at once ?

Was this page helpful?
0 / 5 - 0 ratings