jaredpalmer / formik

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

Proposal: Allow validations to consider what triggered them #3207

Open the-daniel-rothig opened 3 years ago

the-daniel-rothig commented 3 years ago

Feature request

Current Behavior

We have noticed that we want different validation rules to be enforced at different times. For example, in a run-of-the-mill "change password" form (current password / new password / confirm password), we could say:

  1. All three inputs are required, so the form should not submit when one is empty. On the other hand, users often click into a number of inputs before starting to enter data in earnest. It can be a bit annoying if all touched fields complain immediately that they are required.
  2. The new password needs to be at least X characters in length. We don't want to invalidate passwords that are still being typed, but show an error message on blur.
  3. The new password and the confirmed password have to match. While the user types in the new password, we would like to validate (on-change) that so far, they have typed correctly. But as soon as !newPassword.startsWith(confirmPassword), we want to display a validation message. But on Blur, we want to validate a stricter rule, newPassword === confirmPassword.

So we have some rules we want to trigger onSubmit only, some rules we also want to trigger onBlur, and some rules also we want to trigger onChange. That's not currently possible to implement with Formik because the validation schema doesn't know which interaction caused the current validation run.

Desired Behavior

The validate function (and the validationSchema's context) should be provided with and indicator as to whether the validation happens in response to a change, a blur, a mount, or a submit. The implementation of validate or custom yup tests could consume this information to attain the desired behaviour.

Suggested Solution

It seems to me one route to this is to add a ValidationCause enum:

enum ValidationCause {
  onMount="onMount";
  onChange="onChange";
  onBlur="onBlur";
  onSubmit="onSubmit";
}

and add a validationCause parameter to validateFormWithHighPriority, runAllValidations, runFieldLevelValidations, runValidationSchema, and runValidateHandler,

and change runValidationSchema and runValidateHandler to pass a validationCause context parameter / function argument in the user-provided validationSchema object or validate function.

Who does this impact? Who is this for?

My understanding is that this is a non-breaking, additive change that will benefit users that use either form of validation (yup or a validate function). It provides advanced users with the ability to make validation feedback available in a well-timed manner.

The state of play of form validation on the web seems to be that trigger-happy or slightly-later-than-convenient validation is accepted, but I think it stands to reason that improving this would have a measurable impact on UX quality.

Describe alternatives you've considered

I tried to think about workarounds to track outside of Formik which form interaction is currently taking place (by wrapping Formik's own onBlur, onChange and onSubmit - but since I use a yup schema, ultimately I didn't know how to inject this information into my validator (I don't seem to be able to set the context through Formik). I'm considering not passing in the validationSchema into Formik at all and handle validation externally, but that seems a shame to me. I like Formik.

For the "change password" example, onBlur validation is the best compromise. In design terms, we have the desire to only show the submit button when validation succeeds, but because we need the user to blur the last input to show the message, we show the submit button throughout, which is a somewhat acceptable tradeoff. Still, I think having greater nuance about when to show which validation would improve the flow

Additional context

Thanks for reading!

johnrom commented 3 years ago

I think this is a good idea, but I think ultimately there are concerns that could come up, such as what happens in the next change after a "blur". For example, let's say you had a onChange then onblur, your onblur message appeared, then the next validation would be a change validation. Would that override the blur validation? Is it preferable to keep the blur message after the change message fires and the "light validation" doesn't return any errors? Should it continue with the "heavier validation" after the field has been touched?

I think in some ways access to field.isTouched and form.isSubmitting (or a variation that captures the validation attempt) would be preferable to the validation source, especially in the case of custom sources.

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

lnmp4000 commented 3 years ago

I would really appreciate a way to access the isSubmitting property from the withFormik validate function.

Exposing the fomikBag would probably be most useful.

validate: (values, props, formikBag)=>{//do the validation here}