jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.97k stars 2.79k forks source link

validationSchema change depending on form values #1228

Closed hugonasciutti closed 5 years ago

hugonasciutti commented 5 years ago

❓Question

How can I change validationSchema depending on the state of my form?

The docType validation change depending on the value of my select.

I wanted to do this if validationSchema gave me the form props as arguments but it don't.

Example:

        <Formik
          initialValues={{ planType: 'PRE' }}
          validationSchema={props => {
            const isCPF = props.values.docType === 'cpf'

            return yup.object().shape({
              docType: yup.string(),
              number: isCPF ? yup.string().cpf() : yup.string().cnpj()
            })
          }}
          onSubmit={loadPlanActivation}
          render={this.renderForm}
        />

I could solve this with a ref or adding a personal onChange to setState and be able to have the value but it does not seem natural.

weyert commented 5 years ago

You can just use the Yup when? See: https://github.com/jquense/yup#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema

jaredpalmer commented 5 years ago

@weyert is correct. Yup.when() is how to do it.

kirakik commented 5 years ago

I have complex use cases that unfortunately yup.when() is not adequate for, is there any way to pass the values to the validationSchema?

weyert commented 5 years ago

I don't think you can't use validatinSchema prop then but you can write your own validation method which uses your schema and pass the necessary data through the context property.

For example, for a multi-step wizard I am doing this:

      const validationContext = {currentStep, profileType: values && values.profile.profileType}
      schema
        .validate(values, {abortEarly: false, context: validationContext})
        .then(results => {
          return resolve({})
        })
        .catch(validationErrors => {
          const formErrors = yupToFormErrors(validationErrors)
          reject(formErrors)
        })

As you can see I am having a validationContext-variable which contains some state information which I then use in my schema to only enforce some requirement rules for specific profile types. You can then access the in when by prefixing the property name within the context object with a $. In my example I can do $currentStep e.g.

        hasSignAuthority: Yup.boolean().when('$currentStep', {
            is: 3,
            then: Yup.boolean().required().oneOf([true], 'Agreement needs to be accepted'),
            otherwise: Yup.boolean().notRequired(),
        }),
hegelstad commented 5 years ago

This is a recurring issue for me. Why was the prop removed from validationSchema prop? (props) => ValidationSchema?

I have a form that shows a filtered list of fields, and I would also like to reuse the same filtering logic when doing my final validation. Either by removing the unused field keys from the values object just before validation or by conditionally altering the validationSchema.

I tried using .when from Yup, but I don't want to use hard-coded magic values (number ids) to conditionally validate properties or not.

weyert commented 5 years ago

Ain't it part of the Yup context? Or maybe trigger validateYupSchema manually and pass it through the context property

ankurjoshi54 commented 4 years ago

For anyone facing this issue, we can do something like

  <Formik
    initialValues={{ planType: 'PRE' }}
    validationSchema={() => yup.lazy((values) => {
      const isCPF = values.docType === 'cpf'

      return yup.object().shape({
        docType: yup.string(),
        number: isCPF ? yup.string().cpf() : yup.string().cnpj()
      })
    })}
    onSubmit={loadPlanActivation}
    render={this.renderForm}
  />

Basically, we can use

validationSchema={() => yup.lazy((values) => {
  // values is the formik.values object
  // return schema conditionally based on values object
})}
kamleshsingh-idevs commented 7 months ago

yeah its working thanks @ankurjoshi54