jaredpalmer / formik

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

Re-run validations after changing the validationSchema #3687

Open declanelcocks opened 1 year ago

declanelcocks commented 1 year ago

Let me first explain my use case:

Here's my schema for that specific field, let's call it requiredField:

const generateSchema = (diff: Values) => {
  Yup.string()
    .test({
      name: 'name',
      message: 'message',
      test: (value) => (diff?.fieldName ? !!value : true),
    })

  // other stuff

  return schema
}

Basically if diff.fieldName exists (i.e. fieldName has been changed) then I want to make it required (!!value). When the diff changes I update the validationSchema and can see with some logs that diff/schema is working as expected. Here's what actually happens:

What I would like to happen is for the form to be validated after validationSchema changes, to show the error as soon as fieldName has been changed.

Solution

Is this possible as-is? I've tried a combination of enableReinitialize, validateOnChange and validateOnMount but no luck. The only way I've managed to get this behaviour is by doing this:

useEffect(() => {
    setTimeout(() => {
      void validateForm();
    })
  }, [schema])

Seems like a major hack 😥 Thanks for any suggestions!

fastndead commented 1 year ago

You don't need a new schema on values changed, as far as I can see a single one schema can account on what you're trying to do.

Now why the schema isn't validated without useEffect hack and explicit call for validation is a good question, for that minimal reproduction would be greatly appreciated.

also as a side note i think you could use .when() method in your schema for your use case

declanelcocks commented 1 year ago

@fastndead I think what you suggested would work if the diff in my example was actually part of the schema? In this case, it's an object passed in when generating the schema, which can change between renders. I'm not sure how it would work with a simple .when as you suggested 🤔

fastndead commented 1 year ago

@declanelcocks maybe not .when, but you could modify .test() call to accomodate to your use case without having to reconstruct a new schema every time. For example something like this:

.test({
      name: 'name',
      message: 'message',
      test: (value, testContext) => (testContext.parent.fieldName !== initialValues.fieldName ? Boolean(value) : true),
    })
declanelcocks commented 1 year ago

@fastndead from what I can tell, testContext is useful for exposing yup specific values but not anything outside of yup? I'm not seeing how that would work for the diff value?