I'm currently using Formik in my project (loving it), but ran into an issue recently.
All of the onSubmit handlers in my app return a promise. I wrap each handler using a common HOF like so, to handle the submitting and status state in a consistent way when the promise resolves/rejects:
export const handleSubmit = (onSubmit) => (data, {
setStatus,
setSubmitting,
}) => {
setStatus(null);
return onSubmit(data)
.then((result) => setStatus(result))
.catch((error) => {
const { message: id, values } = error;
setStatus({ type: 'error', message: { id, values } });
})
.finally(() => setSubmitting(false));
};
Some of those onSubmit handlers result in a route change on success, which unmounts the Formik instance. I'm using the Next.js router to handle the route change, so in this case I return the promise returned by the router's push/replace methods. This is a lot of detail to say: The then, catch, and finally handlers above end up firing _after_ the route change — that is, _after_ the Formik instance has unmounted.
This is actually not a problem for the finally handler because setSubmitting (very smartly) guards against calling setState after the component has unmounted — but not so for setStatus 😞 (nor for any of the other setters available in the FormikBag).
setStatus = (status?: any) => {
if (this.didMount) {
this.setState({ status });
}
};
I'm finding that setSubmitting does _not_ check if the component is unmounted. isSubmitting appears to reset automatically, so I'm not sure that it's necessary.
Still, it would be really good to fix this warning across the board.
(I'm using formik version 2.0.3.)
I just came across this issue after upgrading from 1.x to 2.0.3. The strange thing is that apparently setSubmitting already has this didMount check in place, but setSubmitting is definitely causing the React state update warning for me as well. I created a common form component that takes an onSubmit function, a schema, etc. and performs the logic that is common to all my forms such as showing an error message when the submit fails. Example snippets from this common form component:
<Formik
onSubmit={this.onSubmitWithErrorHandling}>
initialValues={initialValues}
validationSchema={validationSchema}>
{this.renderForm}
</Formik>
private onSubmitWithErrorHandling(values: FormValues, formikHelpers: FormikHelpers<FormValues>) {
const { onSubmit } = this.props;
const { setSubmitting } = formikHelpers;
onSubmit(values, formikHelpers)
.then(() => setSubmitting(false))
.catch(error => {
this.setErrorMessage(error.message);
});
}
One of my forms that uses this common form component triggers the React update-after-unmount warning as soon as it submits (although other forms that use this component do not trigger the warning for some reason), but if I remove .then(() => setSubmitting(false)), the warning disappears. The docs say that we should call setSubmitting(false) when the submit completes, but I wonder if this is actually necessary. It seems to work fine without it and does not trigger any React warnings.
UPDATE:
I was able to prevent the React state update warning by using the _isMounted approach described here (which seems pretty hacky but works) in my common form component. I'm still not sure if this is a bug in Formik, a race condition, or just a problem with the way I was using React + Formik together.
.then(() => {
if (this.isMounted) { // See https://github.com/jaredpalmer/formik/issues/1449
setSubmitting(false);
}
})
@jbaldassari Thanks for your solution! I will try at home that as well. It is incredible there is a PR since May for solving this but it seems the author is not updating the code based on the review comments....
The docs say,
"IMPORTANT: If onSubmit is async, then Formik will automatically set isSubmitting to false on your behalf once it has resolved. This means you do NOT need to call formikBag.setSubmitting(false) manually. However, if your onSubmit function is synchronous, then you need to call setSubmitting(false) on your own."
Once I removed the calls to setSubmitting after async onSubmit complete, the warnings disappeared.
@charleskoehl that would work if you are only interested in the submitting state, but if you want to change another state, then you can hit this problem as far as I remember.
@charleskoehl @javierguzman I had this problem with a login form. My login function is async, and if I did call login().then(() => actions.setSubmitting(false)), I get the warning on a successful login. If I don't call it, my form submit button is stuck as disabled on an unsuccessful login.
Simply making the onSubmit function async solved my problem. Thanks!
Recap: don't
onSubmit={(values, actions) => {
login({ variables: values }).then(() =>
actions.setSubmitting(false)
);
}}
do
onSubmit={async values => {
login({ variables: values });
}}
@t-lock not a pro but if you call async without await I would have thought it does not make any difference than removing the async and the Promise handler.
onSubmit={values => {
login({ variables: values });
}}
@javierguzman just adding async makes the function return a promise, which means Formik will clear the isSubmitting state automatically when it resolves. Your code snippet above results in the first scenario I described, where isSubmitting is never cleared, so in my case, the submit button will be stuck in 'disabled' when invalid auth credentials are submitted.
True, forgot about that detail! thanks for the clarification @t-lock
@charleskoehl that would work if you are only interested in the submitting state, but if you want to change another state, then you can hit this problem as far as I remember.
What if we had onSubmitResolve and onSubmitReject to be able to perform setState and dispatch ? We could just call these functions only if the form is still mounted. It would be similar to https://docs.react-async.com/api/options
Thanks a lot to @t-lock for his answer... it worked for me.
Just for the record, combined with redux-thunk this is what I've done:
In the component which renders Formik
<Formik .... onSubmit={handleSubmit}>
where
const handleSubmit = async (values) => {
await handleSubmit(values)
}
and the connected component, implements handleSubmit this way
const handleSubmit = async(values) => await dispatch(saveData(values))
and the Thunk, should return a Promise, like this
export const saveData= data=> dispatch => new Promise(
(resolve, reject) => {
dispatch(doSetLoading(true));
axios.post({
url,
payload: data,
}).then( result => {
resolve(); // this allows Formik to set Submitting state to false
dispatch(doAddSuccessMessage(buildSuccessMessage(messages.dataUpdated,data)));
}).catch (e => {
handleError(dispatch, e); // set redux state according to the error
reject(e); // tell Formik that the promise has been rejected
})
}
)
Hope it helps somebody!
Best
Most helpful comment
The docs say,
"IMPORTANT: If onSubmit is async, then Formik will automatically set isSubmitting to false on your behalf once it has resolved. This means you do NOT need to call formikBag.setSubmitting(false) manually. However, if your onSubmit function is synchronous, then you need to call setSubmitting(false) on your own."
Once I removed the calls to setSubmitting after async onSubmit complete, the warnings disappeared.