formik icon indicating copy to clipboard operation
formik copied to clipboard

Add a submittedCount to track successful submits

Open dantman opened this issue 4 years ago • 10 comments

Feature request

Current Behavior

The formik state currently tells you when it's dirty/validating/submitting but it doesn't tell you when a successful submit has happened.

Desired Behavior

The formik state should have an additional piece of state indicating successful submits.

Suggested Solution

isSubmitted/wasSubmitted could work, but I think submitedCount fits in with submitCount and provides more information.

Who does this impact? Who is this for?

I am trying to implement a route blocker/prompt based on Formik, i.e. basically a "if form is dirty or currently submitting then stop the user from navigating away from the from". The problem is a form is after a form is successfully submitted the form is still dirty and there is no way to tell the difference between a form the user has modified and hasn't saved and a form the user has modified and saved to the server.

dantman avatar Feb 26 '21 03:02 dantman

We don't actually have any mechanism for telling Formik whether a submit was successful, only if validation was successful, so this submittedCount wouldn't really have the meaning that is intended.

The problem is a form is after a form is successfully submitted the form is still dirty and there is no way to tell the difference between a form the user has modified and hasn't saved and a form the user has modified and saved to the server.

Your onSubmit should update initialValues. Then, the current values and initialValues would be the same and dirty would be false.

Bonus, you can actually enableReinitialize and set initialValues to values returned by the server, which could be modified on the backend, so your new form values would actually reflect the values stored on the server and not the values that were submitted.

johnrom avatar Feb 26 '21 16:02 johnrom

We don't actually have any mechanism for telling Formik whether a submit was successful, only if validation was successful, so this submittedCount wouldn't really have the meaning that is intended.

Ok, I guess you do but don't. I am using the promise based API and my expectation was that SUBMIT_FAILURE would have a submitedCount: state.submitedCount + 1 in it and that would be the solution.

Though I guess that won't actually handle the non-promise users who only have setSubmitting. And it guess it wouldn't actually work for me initially, since I actually catch errors to set field errors and don't re-throw because of the unhandled error warning.

How do you feel about improving this part of the API? i.e. Making it so that the user can let the user know about success/failure of the submit.

  • For basic promise API usage SUBMIT_SUCCESS would increment submittedCount
  • For callback usage users could opt-in to new formikHelpers.submitComplete() and formikHelpers.submitFailure(errors?: FormikErrors) helpers which would dispatch SUBMIT_SUCCESS and SUBMIT_FAILURE instead and as a bonus could make reporting field errors simpler.
  • For advanced promise API usage some way to throw an error that says "I've caught this and handled it" without breaking the promise chain (like how cancelation errors work) would allow SUBMIT_SUCCESS and SUBMIT_FAILURE to work without issue.

Your onSubmit should update initialValues. Then, the current values and initialValues would be the same and dirty would be false.

That could work for some of my edit forms. But that would be an ugly hack for the creation forms. On creation forms initialValues are a static set of empty values or at worst are memoized from unchanging values. In order to make initialValues update after submit I would have to turn them into component state, even though they are not state.

In fact on creation forms I would be more likely do the opposite, use disabled={isSubmitting || submittedCount > 0} to ensure the submitted form remains disabled after a successful submit while the user is navigated away to avoid the possibility of them creating duplicates.

dantman avatar Feb 26 '21 23:02 dantman

In order to make initialValues update after submit I would have to turn them into component state, even though they are not state.

They are state, if you need them to update in order to test dirtiness after a submit. I thought this was what you initially asked about, sorry if I misunderstood.

Alternatively you can set a separate state item like isRedirecting from your submit fn to disable your form if you're having trouble with duplicates. I haven't had any problems redirecting from the submit fn with users continuing to submit duplicates. I disable the submit button during submit, and the redirect typically begins before the submit callback completes to reset the button.

I think there's room for improvement though because I think it's odd that we do not always dispatch these events.

In your first description you describe failures iterating the submitted count, then at the end you say you would block a user from submitting if the count was greater than one. This is the problem with counters, they aren't flexible and everyone expects them to mean different things, sometimes even different members of the same team. I think the user should track successes or failures in userland. There's no difference between Formik tracking it in state and the user tracking it in state, except that the user has complete control over the meaning of their own state. For this reason, I'm still against adding a counter.

Even if we're super prescriptive and add like, success count and failure count, how do we differentiate between failures of validation vs transient http errors, etc? Whatever we add won't work for everyone and just adds maintenance surface to the API.

johnrom avatar Feb 27 '21 18:02 johnrom

I disable the submit button during submit, and the redirect typically begins before the submit callback completes to reset the button.

I'm actually having a problem precisely because I'm starting the redirect in the submit handler. I'm trying to add a blocker (usePrompt(message, formikContext.isSubmitting)) that'll stop the user from navigating away from an unfinished form. Unfortunately (aside from the dirty form) because the form is still submitting while the submit handler runs it blocks the redirect and things do not unblock till several renders after the navigate is called.

In your first description you describe failures iterating the submitted count how do we differentiate between failures of validation vs transient http errors, etc?

I don't recall suggesting that. I'll be clear, using the currently fully functional promise API as the example, submittedCount should ONLY be incremented when the promise is resolved (at the same time isSubmitting changes to false of course). If the promise is rejected or validation fails the submittedCount should NOT be incremented.

For this reason, I'm still against adding a counter.

How about expanding setStatus with either a setStatus(state => ...) or a mergeStatus(obj)? Right now the only option for setStatus is to completely overwrite the status. So if you're already using status for something else and you try to use it to add a wasSubmitted all you can do is destroy what you were using it for and you definitely can do a counter in a way that doesn't have race conditions.

dantman avatar Feb 27 '21 19:02 dantman

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] avatar Mar 30 '21 00:03 github-actions[bot]

In your first description you describe failures iterating the submitted count .... I don't recall suggesting that. ... I am using the promise based API and my expectation was that SUBMIT_FAILURE would have a submitedCount: state.submitedCount + 1 in it

I think there are a lot of solutions being brought up here without clarity around your actual problem that needs to be solved.

I'm trying to add a blocker (usePrompt(message, formikContext.isSubmitting)) that'll stop the user from navigating away from an unfinished form. Unfortunately (aside from the dirty form) because the form is still submitting while the submit handler runs it blocks the redirect and things do not unblock till several renders after the navigate is called.

Is this your actual issue? Can you recreate this in codesandbox? This sounds solvable without prescribing any specific solution in the Formik API.

In terms of setStatus(state => ...), I think this is fine since there is precedent for providing this pattern elsewhere. However I'm not a huge fan of the pattern myself. I think allowing functional dispatches has a tendency to enable side effects in those functions which could become unpredictable, especially if concurrent mode is adopted. In v3, you'll be able to get the latest state at the moment of dispatch similar to redux by doing setStatus({ ...formik.getState().status, new: "value" });.

johnrom avatar Mar 30 '21 14:03 johnrom

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] avatar Apr 30 '21 00:04 github-actions[bot]

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] avatar Jul 31 '21 00:07 github-actions[bot]

This is a useful feature we have to track our state in the ajax call but it would be nice to know when a form was successful or have a hook to set the status as successful.

sherodtaylor avatar Mar 20 '25 15:03 sherodtaylor

It's also useful to decouple the success functionality with onSuccessand the onSubmit as are different.

sherodtaylor avatar Mar 20 '25 15:03 sherodtaylor