Yup: Yup conditional validation with Formik

Created on 3 Jan 2020  路  6Comments  路  Source: jquense/yup

I'm trying to make a field "old_password" required only if another filed "password" is filled with some value.

It works when using the .when() function in Yup, but when the other field "password" gets empty, the "old_password" still gives me the error message that it is required, even though the "password" is empty.

Also when I implement this, the "name" field validation is not working anymore for some reason. And it works when I type in the "password" field :/

I don't know how to solve this.

Here's a sample code ...

https://codesandbox.io/s/wizardly-frost-sq7fq

Most helpful comment

Formik sets the field to undefined when it's empty (interesting). So you actually are throwing an error in your validation schema (property length does not exist on undefined). You need to change you condition to look like this:

        old_password: Yup.string().when("password", {
            is: value => value && value.length > 0, <- this line
            then: Yup.string().required(
                "Old password is required when setting new password"
            ),
            otherwise: Yup.string()
        })

All 6 comments

Formik sets the field to undefined when it's empty (interesting). So you actually are throwing an error in your validation schema (property length does not exist on undefined). You need to change you condition to look like this:

        old_password: Yup.string().when("password", {
            is: value => value && value.length > 0, <- this line
            then: Yup.string().required(
                "Old password is required when setting new password"
            ),
            otherwise: Yup.string()
        })

@akmjenkins well, that actually works :)
I didn't know that Formik sets the field to undefined when it is empty.
Thanks again :)

otherwise: Yup.string()

@akmjenkins

How can I give condition with a passed variable (A boolean value) instead of using another field (here its password) ? Something like below. Help needed.

        old_password: Yup.string().when(myVariable, {
            is: true
            then: Yup.string().required('required'),
            otherwise: Yup.string()
        })

@jtterra this is where you use context

const schema = Yup.object({
  old_password: Yup.string().when(Yup.ref('$myVariable'), { .... });
});

schema.validate(
  someValue,
  { context: { myVariable: true } }
);

EDIT:

context may sound like a funny thing - but it's exactly what it says it is - it is the context of the application the schema is being evaluated in. It's a source of arbitrary data that your schema uses to resolve conditions (among other things) at cast/validation time.

@jtterra this is where you use context

const schema = Yup.object({
  old_password: Yup.string().when(Yup.ref('$myVariable'), { .... });
});

schema.validate(
  someValue,
  { context: { myVariable: true } }
);

EDIT:

context may sound like a funny thing - but it's exactly what it says it is - it is the context of the application the schema is being evaluated in. It's a source of arbitrary data that your schema uses to resolve conditions (among other things) at cast/validation time.

@akmjenkins Thank you for your quick response. Here I am passing schema to Formik

<Formik<EnjoyerForm>
          initialValues={}
          onSubmit={onSubmit}
          validationSchema={FormSchema(state.hasNote)}
   ...

And this is my try

const FormSchema = (hasNote: boolean) => Yup.object().shape({
  note: Yup.string().when(hasNote, {
    is:  true,
    then: Yup.string().required('Required'),
    otherwise: null,
  }),
})

What should be fixed here?

@jtterra, you're kind of blocked from doing it "properly" by this.

But you can still do it, the thing is, you're going to recreate your schema every time your state changes, so you don't actually have a conditional schema per se, but you're going to recreate your schema every time your state changes. This could be bad for perf (but if you don't notice anything then it's fine). In the meantime, keep an eye on the PR I mentioned above.

The way to do this is to put the schema as part of your state, and then update the schema every time hasNote changes. In the functional component world it would look like this:

const [hasNote,setHasNote] = useState(false);
const [schema,setSchema] = useState(() => FormSchema(hasNote));

useEffect(() => {
  // every time hasNote changes, recreate the schema and set it in the state
  setSchema(FormSchema(hasNote));
},[hasNote]);

return (<Formik validationSchema={schema} .../>)

and now FormSchema looks like this:

const FormSchema = (hasNote: boolean) => Yup.object().shape({
  // no more conditional schema, just recreating a schema every time hasNote changes
  note: hasNote ? Yup.string().required() : Yup.string()
})
Was this page helpful?
0 / 5 - 0 ratings