Skip to content

Instantly share code, notes, and snippets.

@randycoulman
Created October 13, 2018 17:26
Show Gist options
  • Save randycoulman/608271914f29f580f07f44d38197742d to your computer and use it in GitHub Desktop.
Save randycoulman/608271914f29f580f07f44d38197742d to your computer and use it in GitHub Desktop.
Working around non-async `submitForm` in Formik
import { compose, setDisplayName, wrapDisplayName } from "recompose";
export const makeHOC = (name, ...enhancers) => Component =>
compose(
setDisplayName(wrapDisplayName(Component, name)),
...enhancers
)(Component);
import { withFormik } from "formik";
import { omit } from "ramda";
import {
lifecycle,
mapProps,
withHandlers,
withStateHandlers,
} from "recompose";
import { makeHOC } from "./makeHOC";
/*
This is a pretty convoluted bit of code, so some explanation is in order.
In the currently-available version of Formik (1.0.1), `submitForm` returns a
Promise that resolves once validation is complete, but not for when the actual
form submission completes. See https://github.com/jaredpalmer/formik/pull/500
for a PR that would resolve this.
The goal is to provide an async `submitForm` callback for a form that
will resolve if the form validates and submits successfully, and will reject
otherwise. The only place that knows whether form submission succeeds or fails
is the `handleSubmit` callback provided by our caller, and that isn't called
unless the form is valid.
In order to make this work, we do the following:
- Provide a `replacePromise` callback that create a new Promise, saves off its
resolve and reject callbacks, and passes all three down as props.
- Make sure the new Promise calls `replacePromise` again once it resolves or
rejects. We need a new pending Promise every time we go to submit the form.
In order to do this, we have pass `replacePromise` into itself, which is
very strange-looking.
- Wrap the incoming `handleSubmit` callback with our own version that calls
the Promise's `resolve` or `reject` callbacks (named `onSubmitSuccess`
and `onSubmitFailure`) appropriately.
- Wrap the incoming `submitForm` callback with our own version that calls
Formik's `submitForm` and returns the Promise. Because our `handleSubmit`
wrapper won't be called when the form is invalid (and thus won't be able to
resolve or reject the Promise), `submitForm` calls `onSubmitFailure` if the
form isn't valid.
- Finally, we clean up after ourselves by not passing along any of the props
we use internally.
*/
const replacePromiseHandler = () => replacePromise => {
let onSubmitFailure = null;
let onSubmitSuccess = null;
const promise = new Promise((resolve, reject) => {
onSubmitFailure = reject;
onSubmitSuccess = resolve;
}).finally(() => replacePromise(replacePromise));
return {
onSubmitFailure,
onSubmitSuccess,
submittingPromise: promise,
};
};
const handleSubmitProxy = handleSubmit => async (values, formikBag) => {
const { props } = formikBag;
const { onSubmitFailure, onSubmitSuccess } = props;
try {
const result = await handleSubmit(values, formikBag);
onSubmitSuccess(result);
} catch (e) {
onSubmitFailure(e);
}
};
const submitFormProxy = ({
errors,
isValid,
onSubmitFailure,
submittingPromise,
submitForm,
}) => async () => {
await submitForm();
if (!isValid) {
onSubmitFailure(errors);
}
return submittingPromise;
};
export const withFormikAsync = ({ handleSubmit, ...options }) =>
makeHOC(
"withFormikAsync",
withStateHandlers({}, { replacePromise: replacePromiseHandler }),
lifecycle({
componentDidMount() {
const { replacePromise } = this.props;
replacePromise(replacePromise);
},
}),
withFormik({ ...options, handleSubmit: handleSubmitProxy(handleSubmit) }),
withHandlers({ submitForm: submitFormProxy }),
mapProps(
omit([
"onSubmitFailure",
"onSubmitSuccess",
"replacePromise",
"submittingPromise",
])
)
);
@randycoulman
Copy link
Author

This is our current solution for working around the non-async handleSubmit function in Formik (see jaredpalmer/formik#500). This may not work for everyone, but it seems to do the trick for us. Note that we use higher-order components (including withFormik) and recompose in our system, so that's what we're using here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment