Created
October 13, 2018 17:26
-
-
Save randycoulman/608271914f29f580f07f44d38197742d to your computer and use it in GitHub Desktop.
Working around non-async `submitForm` in Formik
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { compose, setDisplayName, wrapDisplayName } from "recompose"; | |
export const makeHOC = (name, ...enhancers) => Component => | |
compose( | |
setDisplayName(wrapDisplayName(Component, name)), | |
...enhancers | |
)(Component); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | |
]) | |
) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 (includingwithFormik
) and recompose in our system, so that's what we're using here.