formik icon indicating copy to clipboard operation
formik copied to clipboard

Promise returned from submitForm not rejected when form is invalid

Open Aaronius opened this issue 6 years ago • 40 comments

🐛 Bug report

Current Behavior

When calling submitForm on an invalid form, the returned promise is resolved.

Expected behavior

When calling submitForm on an invalid form, the returned promise should be rejected according to the documentation.

Reproducible example

https://codesandbox.io/s/formik-codesandbox-template-81kio?fontsize=14

Suggested solution(s)

Reject the promise when there are errors. It seems the related code is found here: https://github.com/jaredpalmer/formik/blob/22a17b84700af503828ca27c0d2e6a283112c4ea/src/Formik.tsx#L439-L448

Additional context

Your environment

Software Version(s)
Formik 1.5.7
React 16.8.6
TypeScript n/a
Browser Chrome 74
npm/Yarn 6.4.1
Operating System Mac Mojave

Aaronius avatar Jun 04 '19 18:06 Aaronius

Did I write that in the docs? On mobile and can’t git blame.

jaredpalmer avatar Jun 06 '19 09:06 jaredpalmer

It seems to have been written by @FBerthelot and merged by you.

Commit: https://github.com/jaredpalmer/formik/commit/4dbb93f21ee120d2e30e3707e9c3d3380c4c4c1b PR: https://github.com/jaredpalmer/formik/pull/1423

It looks like after the PR was merged, there was discussion around this problem. FWIW, I'd rather the promise actually get rejected than just fixing the documentation to match the code.

Aaronius avatar Jun 06 '19 14:06 Aaronius

Running into this problem myself, but instead it seems submitForm doesn't return a correct promise at all. If you look at the submitForm function, it only returns the validation promise, it doesn't return anything from executeSubmit, so the promise will always return undefined no matter what.

https://github.com/jaredpalmer/formik/blob/914ccde516e36e40bcc21996bb1b95372b95689b/src/Formik.tsx#L443-L445

To fix it, I just copied the code out into my own submitForm function and returned this.props.onSubmit and now it works just fine. executeSubmit seems useless since it just calls this.props.onSubmit but isn't used anywhere else.

justindmyers avatar Aug 05 '19 19:08 justindmyers

Spent a few hours digging around this myself tonight. Didn't realize the docs were wrong till I pulled down the source. The current documentation for 1.5.8 shows

submitForm: () => Promise
Trigger a form submission. The promise will be rejected if form is invalid.

But like justindmyers stated above, submitForm will always resolve with undefined.

clflowers5 avatar Aug 08 '19 03:08 clflowers5

This is fixed in 2.x

jaredpalmer avatar Aug 08 '19 19:08 jaredpalmer

Can you fix this also for 1.5.x? 2.x is still in pre-release phase.

latuszek avatar Aug 20 '19 10:08 latuszek

@jaredpalmer I'm using 2.0.1-rc.13 and this appears to still be broken: https://github.com/devinsm/formik-submitform-bug

devinsm avatar Sep 06 '19 00:09 devinsm

The work around for now is to wrap submitForm in custom logic:

// submitForm and validateForm are Formik's submitForm and validateForm
function fixedSubmitForm({ submitForm, validateForm }) {
  return new Promise((resolve, reject) => {
    submitForm()
      .then(validateForm)
      .then(errors => {
        const isValid = Object.keys(errors).length === 0;
        if (isValid) {
          resolve();
        } else {
          reject();
        }
      })
      .catch(e => {
        console.log('error: ', e);
        reject();
      });
  });
}

Of course this causes the form to be validated twice. In some situations this may lead to unacceptable lag after the user hits submit, but for smaller forms with synchronous validation it should work fine.

devinsm avatar Sep 06 '19 16:09 devinsm

Interestingly, the typing is also inconsistent with the documentation. In 1.5.8 (latest release at time of writing), dist/types.d.ts has

export interface FormikActions<Values> {
  ...
  submitForm(): void;
  ...
}

josephsiefers avatar Sep 09 '19 19:09 josephsiefers

Thank you @devinsm this is a useful workaround for the time being

@josephsiefers I can verify this to be the case as well

jwmann avatar Dec 18 '19 20:12 jwmann

Is this fixed? Using 2.1.1 I also ran into an issue where calling SubmitForm, the returned promise is resolved even though the form is invalid.

oowowaee avatar Jan 08 '20 22:01 oowowaee

Is this fixed? Using 2.1.1 I also ran into an issue where calling SubmitForm, the returned promise is resolved even though the form is invalid.

Same.

HannahG0703 avatar Jan 10 '20 19:01 HannahG0703

Looks like 2.1.2 is not rejecting when the form is invalid.

sebastianpatten avatar Jan 14 '20 23:01 sebastianpatten

I second @sebastianpatten. The fix doesn't work even in v2.1.2

dorklord23 avatar Jan 17 '20 09:01 dorklord23

Not working in v2.1.3 too.

hristof avatar Jan 28 '20 22:01 hristof

Seems in 2.1.4 it is still an issue

jimmyn avatar Feb 13 '20 16:02 jimmyn

Still got the problem too.

anaelChardan avatar Apr 09 '20 08:04 anaelChardan

TWIMC fixed with #1904 then reverted with #1198 2.0.7 is the only version with expected behaviour

oefimov avatar Apr 21 '20 06:04 oefimov

Would love to have this fixed. 2.1.4.

MrNghia123 avatar Apr 25 '20 14:04 MrNghia123

I guess this was reverted because <form onSubmit={formProps.handleSubmit} would print a console error when clients do not catch the thrown error.

We need the functionality too in order to send analytics information and other side effects on failed form validation when the user tried to submit. Currently we use this workaround: https://github.com/jaredpalmer/formik/pull/2103#issuecomment-574565897, but it is not very nice, and also it might break when React Concurrent Mode lands, and Automatic batching of multiple setStates happens (which even happens in Blocking Mode).

I would suggest to add an additional config parameter to FormikConfig: onSubmitCancelledByFailingValidation.

    const formProps = useFormik({
        initialValues: myInitialValues,
        validate: myValidateFunction,
        onSubmit: mySendForm, // is only executed when validation succeeds
        onSubmitCancelledByFailingValidation: myFallbackOnFailedValidation,
    })

This would be backwards-compatible, and also it would separate the general validation (that can happen when e.g. just typing in an input, or blurring a field) from validation that is triggered by trying to send the form.

An alternate solution idea would be to add a second parameter to the validate config parameter to signal that the validation is being executed because the user is trying to send the form: validate?: (values: Values, validationTrigger: 'field-changed' | 'field-blurred' | 'submit') => void | object | Promise<FormikErrors<Values>>

    const formProps = useFormik({
        initialValues: myInitialValues,
        validate: (values, validationTrigger) => {
            const result = myValidateFunction()
            if (validationTrigger === 'submit') {
                myFallbackOnFailedValidation()
            }
            return result
        },
        onSubmit: mySendForm, // is only executed when validation succeeds
    })

This would also be backwards-compatible, but not as elegant on the client side, and also would not work for a Yup validationSchema if used. Therefore I would prefer the first approach.

@jaredpalmer What do you think about these suggestions? Would you accept a PR?

fabb avatar May 06 '20 07:05 fabb

This is still an issue, almost been a year. Is there any traction on resolving this?

why06 avatar May 13 '20 19:05 why06

I created a PR with my solution suggestion, constructive discussion about the approach welcome: #2485.

fabb avatar May 21 '20 13:05 fabb

Is there any update with this? I've had to revert back to 2.0.7 in order to get access to the data returned from submitForm Promise.

rmincling avatar Sep 17 '20 10:09 rmincling

Doesn't seem to be rejecting when the form is invalid in 2.1.5 either 😞 .

alistairholt avatar Sep 29 '20 23:09 alistairholt

I hate to do it, but.... bump!

danawoodman avatar Oct 20 '20 16:10 danawoodman

2.2.5 bug is still there (

unbugx avatar Nov 23 '20 13:11 unbugx

Probably it is off topic, I describe my case

export const InfoForm = () => {
  .................
  .................

  const formik = useMemo(() => ({
    initialValues,
    onSubmit: async (values:, actions) => {
      .....
    },
    validate: (values: typeof initialValues) => {
      const errors = {};

      if (!values.reason) {
        errors.reason = ....;
      }

      return errors;
    },
  }), []);

  return (
    <>
      <Formik {...formik}>
        <Form>
    	  ......
    	  ......
    	 <Submit className={s.submit}>Submit</Submit>
        </Form>
      </Formik>
    </>
  );
};

If any error happens in onSubmit or in validate function then I just see warning An unhandled error was caught from submitForm() So I can't use Sentry here :(.

What helped me:

I extracted submit button and start use submitForm on click:

export const Submit = ({
  children,
  ...restProps
}) => {
  const { isSubmitting, submitForm } = useFormikContext();

  return (
    <ButtonPrimary
      type='submit'
      loading={isSubmitting}
      {...restProps}
      onClick={submitForm}
    >
      {children}
    </ButtonPrimary>
  );
};

So now I can use Sentry without any additional magic, though it is also magic :)

Снимок экрана 2020-11-23 в 18 15 18

unbugx avatar Nov 23 '20 15:11 unbugx

Any news?

// submitForm and validateForm are Formik's submitForm and validateForm function fixedSubmitForm({ submitForm, validateForm }) { ...

was till now the only way to submit a form from an button, but it also started to ignore validation ;(

Macilias avatar Dec 01 '20 13:12 Macilias

I used this as a workaround:

<Button onClick={() => {
  formik
    .submitForm()
    .then(() => {
      if (formik.isValid) {
        handleFormSuccessfullySent();
      } else {
        handleFailedValidationOnFormSubmission();
      }
    })
}} />

I also had validateOnMount set to true, otherwise formik.isValid condition was true when the form wasn't touched before trying to submit it:

  const formik = useFormik({
    // ...
    validateOnMount: true
  });

maciejtoporowicz avatar Jan 31 '21 16:01 maciejtoporowicz

If you just need to reject the promise returned by the submitForm function when the form is invalid, this is an easy workaround:

const submit = () => helpers.submitForm().then(() => helpers.isValid || Promise.reject())
<SubmitButton onClick={submit}>Submit</SubmitButton>

I also needed to use @maciejtoporowicz's suggestion to add thevalidateOnMount flag.

callumjg avatar Mar 10 '21 01:03 callumjg