Add form-global error key (e.g. _form)
Is your feature request related to a problem? Please describe.
Sometimes the call to API back-end fails with an error that is not related to a specific field (e.g. a 404). In that case, setErrors is not applicable. I could use setStatus but that requires some extra checks and some extra handling in the form display.
Describe the solution you'd like
Allow setting a global form error with a specific key (e.g. _form, like [redux-form'(https://redux-form.com/7.4.2/docs/api/reduxform.md/#-code-onsubmit-function-code-optional-)) and make it available as a prop, for instance error (again, keeping the same naming as redux-form).
const InnerForm = ({
values,
errors,
error,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
{ error && <div>{error}</div> }
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
const MyForm = withFormik({
mapPropsToValues: props => ({ email: '', password: '' }),
handleSubmit: (
values,
{
props,
setSubmitting,
setErrors,
}
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
},
errors => {
setSubmitting(false);
setErrors(transformMyApiErrors(errors));
}
);
},
})(InnerForm);
Describe alternatives you've considered
As said above, in theory you can use setStatus, but that requires more setup:
const InnerForm = ({
values,
errors,
status,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
{ status && status.error && <div>{error}</div> }
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
const MyForm = withFormik({
mapPropsToValues: props => ({ email: '', password: '' }),
handleSubmit: (
values,
{
props,
setSubmitting,
setErrors,
setStatus,
}
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
},
errors => {
setSubmitting(false);
if (errors.status_code !== 400) {
setStatus({error: errors})
}
else {
setErrors(transformMyApiErrors(errors));
}
}
);
},
})(InnerForm);
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
Would still like your opinion on ^ :)
I like this! <3 really useful.
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
Still think this is important !
On Tue 30 Oct 2018 at 03:11, stale[bot] [email protected] wrote:
Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/jaredpalmer/formik/issues/711#issuecomment-434146267, or mute the thread https://github.com/notifications/unsubscribe-auth/AAHWtW09LLYWEBbClngE1jNlDTEj4_Faks5up7VNgaJpZM4U3rnw .
This could work well in conjunction with automatic setSubmitting #160.
This! I just came to drop a comment and say I'd love to see this feature in formik. I would definitely like to be able to handle unexpected API errors not really related to any of the fields.
Just my two cents that this indeed should be added. There are some cases where the error isn't tied to a specific field and we're displaying an alert box above the form.
Just started using formik and it's a complete beauty but I also quickly recognized this a missing feature that I think would add great value. typical instance where user tried to login and received a 403 from the back end. I'd like to mark the form in error state without being specific at field level.
You can do that already. This issue was about providing a more intentional API about showing global form errors. But it’s already possible with state like OP showed.
Just wanted to chime in and say this would be a fantastic feature!
It would be much better to have not only one form-level error, but form-level errors object, and API to discard them if some fields has changed, etc.
Hey,
Just want to mention that with hooks it's easy to add this feature on top of formik. Here is an example I have for my app where I try to share/reuse the global error / onSubmit logic which is quite often always the same
type AppFormikConfig<Values extends object, Result = any> = Omit<
FormikConfig<Values>,
'onSubmit'
> & {
submitAppForm: (values: Values) => Promise<Result>;
onSubmitSuccess?: (result: Result) => void;
};
export const useAppFormik = <Values extends object, Result = any>(
config: AppFormikConfig<Values, Result>,
) => {
const { submitAppForm, onSubmitSuccess, ...otherFormikConfig } = config;
const [globalError, setGlobalError] = useState<string | null>(null);
const onSubmit = async (values: Values, actions: FormikHelpers<Values>) => {
console.debug('onSubmit', values);
actions.setSubmitting(true);
setGlobalError(null);
try {
const result: Result = await submitAppForm(values);
console.debug('form submitted successfully', { values, result });
form.resetForm();
onSubmitSuccess && onSubmitSuccess(result); //TODO give default user feedback on success?
} catch (e) {
if (isAPIResponseDataError(e)) {
console.debug('Reponse errors', e.response.data);
form.setErrors(e.response.data);
if (e.response.data.general) {
setGlobalError(e.response.data.general);
}
} else {
console.error('error', e);
setGlobalError('Erreur technique: ' + e.message); // TODO how to handle those errors?
}
} finally {
actions.setSubmitting(false);
}
};
const form = useFormik<Values>({
...otherFormikConfig,
onSubmit,
});
return { form, globalError };
};
Would love to see this implemented still. Would make things a lot cleaner. Really lacking when compared to final-form.
I agree this would provide a nice user experience for handling errors.
A workaround that I found simple and effective has been to add an error field to my form properties as a placeholder field for attaching general form errors. I can set the error value of this and use the <ErrorMessage> component to place the error where appropriate for the overall form.
Just my 10 cents, I'm a bit mixed on this one. On the one hand, I think the FormikErrors object should be treated as a 1-1 mapping of errors to fields. On the other hand, I think it would be more convenient to have a place for errors related to the form. On the third hand, I think all logic related to the form's submission is considered userland code, and I'd rather we didn't introduce possible naming collisions on the field errors object. I think if we did solve this as a convenience, we would have to allow any custom definitions we wanted and not just one. I'd propose something like a mapPropsToFormErrors / specific formErrors prop.
const myValidate<Formik<Values, FormErrors>> = (values, formikActions) => {
// This API is completely improvised
if (values.startDate > values.endDate) {
formikActions.addFormError("date", "End date must occur after start date.");
}
}
const MyInnerForm = {({formErrors}) => (
<>
<ErrorMessage errors={formErrors.general} />
<ErrorMessage errors={formErrors.date} />
</>
);
const MyForm = ({generalErrors, dateErrors}) => (
<Formik formErrors={{ general: generalErrors, date: dateErrors }} validate={myValidate}>
{formikProps) => (
<MyInnerForm {...formikProps} />
)}
</Formik>
);
const MyWrappedForm = ({formErrors}) => (
<>
<ErrorMessage errors={formErrors.general} />
<ErrorMessage errors={formErrors.date} />
</>
);
const MyForm = withFormik({
mapPropsToFormErrors={({generalErrors, dateErrors}) => ({
general: generalErrors,
date: dateErrors,
})}
validate={myValidate}
})(MyInnerForm);
At the same time, would this have much benefit over handling it entirely in userland code?
@johnrom How do you recommend handling this in userland if the inner form needs to place the generic error message within it? I typically pass the form component via the render method to the Formik component. Should I instead be putting it inside the Formik as a child and adding whatever generic error props I need in addition to the formik props?
@defunctzombie I view the render method as being somewhat deprecated, but you can pass props from the parent to either the render or child components. If you're breaking things out into separate components, you can use React.Context.
const MyForm = ({
externalErrors, // comes from higher up
onSubmit // some parent component calculates the externalErrors
}) => (
<Formik onSubmit={onSubmit}>
{formikProps => ( // could also be render prop
<Form>
{externalErrors.length > 0 && externalErrors.map(error =>
<p>{error}</p>
)}
</Form>
)}
</Formik>
);
@johnrom Thanks for the tips! Following your examples I was able to put together a pattern that is working well for me by wrapping up my custom form with formik and being able to avoid too much duplication with setting up properties. Certainly suffers from potential future collision with FormikConfig properties but is viable enough for my needs until that happens.
const SomeCustomForm = (props: { error: string } & FormikConfig<FormValues>) => {
return (
<Formik {...props}>
{formProps => <form onSubmit={formProps.handleSubmit}>...</form>}
</Formik>
)
};
// Use in some other component
render() {
return <SomeCustomForm onSubmit={...} initialValues={...} error={...} />;
}
Still interested in a fix like this. Now using the @defunctzombie workaround adding an error field, but it's not really optimal.
If the qualm is that we don't want collisions, then why not use a symbol instead of a string key for the errors?
Formik.FORM_ERROR === Symbol.for("Formik.FORM_ERROR");
<Formik
onSubmit={async (values, helper) => {
try {
await callApi();
} catch (error) {
helper.setErrors({
[Formik.FORM_ERROR]: "There was an unexpected error when submitting.",
});
}
}}
>
<ErrorMessage
name={Formik.FORM_ERROR}
component="div"
className="field-error"
/>
</Formik>;
I think mixing field errors and form errors is less graceful since form-level errors are often API or app-level errors whereas field errors are generally fixable user errors. I think instead of would make sense to add a new place for an array of form errors. Currently, status can be used for this since it is just a bag of whatever the developer wants. It doesn't get used for validation, but it can manually be checked in the submit fn. API errors aren't generally used for form validation anyway, but to alert the user to something.
If the error depends on the user changing something (validation), the error should be added to the field that needs changing.
Given all these variables, to me it makes sense to add this as a plugin or wrapper which can be more configurable than adding it to Formik's core functionality, which would prescribe a certain way of handling these scenarios.
Any update on this?