jaredpalmer / formik

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

Why does Formik validate EVERY input including pristines when a single input onChange is fired? #3445

Open Jakemangan opened 2 years ago

Jakemangan commented 2 years ago

I'm really trying to wrap my head around why this is a design choice and how it's not more of an issue.

I have a form that has a number of inputs on it, and when I start typing in any of the fields the whole form is validated as per my validate function, causing every validation error message to be rendered onto my form.

The form isn't even doing anything magical either, I'm literally just taking in basic text input and validating if the values are empty or not. Yet when I do that, Formik thinks it's a great idea to validate EVERY field, including my pristine ones that are yet to be touched.

Can someone please explain to my how I can change this behaviour so that only the form currently firing onChange events is validated?

I've tried using the form's touched object to check which are yet to be touched and only validate the touched ones, but that produces more problems, especially when trying to submit the form.

validateOnChange and validateOnBlur don't help either, they just turn off the validation

Again, why is this the default behaviour. Who would ever want to validate their entire form at once based on onChange events?

I've even tried to do field-level validation using instead, and even that produces the same behaviour? I'm genuinely at a loss as to how anyone can produce working forms with this. Obviously people do, so if someone can explain how to do this on a field by field basis I'd very much appreciate it.

  const formik = useFormik({
    initialValues: {
      positionTitle: "",
      experienceRequired: ""
    },
    onSubmit: (values) => {
      console.log("Form output: ", JSON.stringify(values));
      console.log("Errors: ", formik.errors);
    },
    isInitialValid: false,
    validate: validate
    validateOnMount: false,
    validateOnChange: false,
    validateOnBlur: false
  });

<FormikProvider value={formik}>
    <form onSubmit={formik.handleSubmit} className="flex flex-col mt-4">
        <label htmlFor="positionTitle" className="input-label mt-4">
        Job title
        </label>
        <Field
        as="input"
        validate={validateBasicRequired}
        name="positionTitle"
        className="basic-input w-1/2"
        placeholder="Full-stack solidity engineer"
        onChange={formik.handleChange}
        value={formik.values.positionTitle}
        ></Field>
        {formik.errors.positionTitle &&
        formik.errors.positionTitle === "Required" && (
            <BasicValidationError message="Required field."></BasicValidationError>
        )}

        <Select
        name="experienceRequired"
        onChange={handleSelectOnChange}
        options={getMultiSelectOptions(experienceValues)}
        ></Select>
        {/* <Field
        as={Select}
        name="experienceRequired"
        validate={validateBasicRequired}
        onChange={handleSelectOnChange}
        styles={multiSelectStyles}
        className="w-1/2"
        value={formik.values.experienceRequired}
        options={getMultiSelectOptions(experienceValues)}
        ></Field> */}
        {formik.errors.experienceRequired &&
        formik.errors.experienceRequired === "Required" && (
            <BasicValidationError message="Required field."></BasicValidationError>
        )}
    </form>
</FormikProvider>

This is the validation function I was using before field-level

  const validate = (values: PostJobForm) => {
    const errors: any = {
      compensation: {},
    };

    if (!values.positionTitle) {
      errors.positionTitle = "Required";
    }

    if (!values.experienceRequired) {
      errors.experienceRequired = "Required";
    }

    return errors;
  };
johnrom commented 2 years ago

The simple answer is because dependent fields exist and Formik doesn't currently have a way to declare them out of the box. Therefore if you had a validation like :

Water temperature
[temp]: when C <= 100, when F <= 212
[scale]: C or F

When scale changes, the validation for temp needs to re-run. Formik doesn't have a way to express that so it just recalculates the whole thing.

I have suggested a dependent validation system which can express dependent fields, like

<Field include={values => values.scale} />

But it is unclear how that would trigger dependent validations without using proxies -- which cannot be used in internet explorer, and some people still use ie11. It's something I plan to think about after v3.

nycgavin commented 2 years ago

I am currently facing the same issue, i think we should change the default behavior to only validate one field because that’s like way way way more common than dependent field, and for the dependent field, it can be simply an array of strings to represent fieldnames to maximize reuse for components without tying to a parent interface that might need to change when the component is reused . Btw react-hook-form validates 1 field at a time by default and its got way better api than formik, if not for the crazy typescript compile time and slow intellisense, I would have preferred the other library