formik icon indicating copy to clipboard operation
formik copied to clipboard

Add form-global error key (e.g. _form)

Open charlax opened this issue 7 years ago • 24 comments

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);

charlax avatar Jun 26 '18 10:06 charlax

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.

stale[bot] avatar Aug 25 '18 11:08 stale[bot]

Would still like your opinion on ^ :)

charlax avatar Aug 27 '18 09:08 charlax

I like this! <3 really useful.

kvnwolf avatar Aug 31 '18 01:08 kvnwolf

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.

stale[bot] avatar Oct 30 '18 02:10 stale[bot]

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 .

charlax avatar Oct 30 '18 08:10 charlax

This could work well in conjunction with automatic setSubmitting #160.

wachunga avatar Nov 28 '18 23:11 wachunga

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.

jsardev avatar Dec 06 '18 16:12 jsardev

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.

iKonrad avatar Jan 07 '19 21:01 iKonrad

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.

mshauny avatar Jan 13 '19 13:01 mshauny

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.

robertvansteen avatar Jan 13 '19 13:01 robertvansteen

Just wanted to chime in and say this would be a fantastic feature!

gangwarily avatar Feb 26 '19 21:02 gangwarily

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.

polykoi avatar Apr 16 '19 13:04 polykoi

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 };
};

slorber avatar May 14 '19 10:05 slorber

Would love to see this implemented still. Would make things a lot cleaner. Really lacking when compared to final-form.

nickineering avatar Aug 05 '19 12:08 nickineering

I agree this would provide a nice user experience for handling errors.

KStockton avatar Aug 26 '19 22:08 KStockton

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.

defunctzombie avatar Sep 21 '19 20:09 defunctzombie

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 avatar Sep 22 '19 15:09 johnrom

@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 avatar Sep 23 '19 01:09 defunctzombie

@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 avatar Sep 23 '19 13:09 johnrom

@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={...} />;
}

defunctzombie avatar Sep 24 '19 03:09 defunctzombie

Still interested in a fix like this. Now using the @defunctzombie workaround adding an error field, but it's not really optimal.

Adamih avatar Sep 01 '20 13:09 Adamih

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>;


kkirby avatar Oct 21 '21 23:10 kkirby

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.

johnrom avatar Oct 22 '21 16:10 johnrom

Any update on this?

ThimoBL avatar Aug 01 '25 10:08 ThimoBL