jaredpalmer / formik

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

[2.4.0] onSubmit types are broken for TypeScript + Yup users #3809

Closed MarcPorciuncula closed 1 year ago

MarcPorciuncula commented 1 year ago

First of all, big fan and long time user of Formik, thanks to the maintainers and contributors for all the hard work 🙏

Bug report

I was testing an upgrade to 2.4.0 at my org and came across what seemed like some strange behaviour. Turns out it was the introduction of Yup transforms support: https://github.com/jaredpalmer/formik/pull/3796

We use Formik + Yup + TypeScript and have been working with the assumption that even if the Yup schema might do transformations for the purposes of validation we will still receive the values of the original type in onSubmit. The TypeScript types of the package agree with this:

export interface FormikConfig<Values> extends FormikSharedConfig {
  onSubmit: (values: Values, formikHelpers: FormikHelpers<Values>) => void | Promise<any>;
}

However under 2.4.0 when using a Yup schema with transformations, the runtime types of values now disagree with TypeScript. Here's an example:

useFormik({
  initialValues: { myNumber: '5' },
  validationSchema: Yup.object({ myNumber: Yup.number().integer().positive() }),
  onSubmit: (values) => {
    // the inferred type of values is { myNumber: string }
    console.log(typeof values.myNumber) // outputs "number"
  },
})

This could have caused some real issues for us if it wasn't caught by our tests, as we would have values of the wrong type flowing from our forms into API calls etc. all unvalidated by TypeScript. I'm fairly confident we wouldn't be the only ones surprised by this.

I do believe https://github.com/jaredpalmer/formik/pull/3796 was a breaking change (there is a little discussion about that in the PR), and even if accepted as non-breaking the type mismatch mentioned above is a major issue for TS users.

Suggested solution(s)

The TypeScript types for onSubmit could be updated to use the Yup schema transformation type for values. But it seems like this would be non-trivial given validationSchema is typed pretty loosely at the moment, you would have to make it so the type of onSubmit changes depending on whether a Yup schema is provided.

Users can work around this by manually casting the values received by onSubmit to Yup.TypeOf<typeof validationSchema>, but imo that would be pretty clumsy/brittle. If you forget to do that then you'd get runtime behaviour that won't match your TypeScript.

Additional context

We'll need to stay on 2.3.3 until some resolution is reached. It's too risky/too much work for us to upgrade as we would have to audit every Formik form across our codebase.

Your environment

Software Version(s)
Formik 2.4.0
React 18.2.0
Yup 0.32.11
TypeScript 4.9.5
Browser N/A
npm/Yarn pnpm@7.30.0
Operating System macos Ventura 13.2.1
alperen commented 1 year ago

Since Formik's submitted values are cast by Yup's cast function, we're also facing with that issue in our forms. Types in runtime and defined ones via TypeScript may differ. Some forms can not be submitted or get an unhandled error while submitting that is raised by Yup while casting.

And in this case, type source of Form is our ones not inferred by Yup. IMO, Yup is just a tool that we use for validation, developers should adjust type by their use case.

I have prepared a Codesandbox for this. It is in formik@2.4.0 by default.

Steps to Produce Issue 1

Try to submit it when the version is 2.4.0, you won't see the submitted alert because Yup will throw an error.

TypeError: The value of date could not be cast to a value that satisfies the schema type: "date". 

attempted value:  

result of cast: Invalid Date

Yup can not cast an empty string and throws an error, but if you downgrade the version to 2.2.9, the code works as expected without any changes.

Steps to Produce Issue 2

As you can see the type of date input is defined as a string.

In version 2.4.0, if you submit it value changes into a Date object but is saved as a string in Formik's internal state.

image

What we expect was string because of our type definitions, and input handling.

But if we downgrade it, the string stays string.

m-nathani commented 1 year ago

I am also facing this issue where values of the field is a moment object.. however it's not submitting the form and throwing the above error, as its not able to perform moment.format() in the form.

had to revert it back to 2.2.9 from 2.40 to fix it.

quantizor commented 1 year ago

Damn. Alright will roll this back.