formik: A way to keep backend errors with client validation

Here is a simple form with errors example:

name - no error
surname - no error

When I submit form, I receive backend errors: { surname: 'Exists' }. So, I set field errors and now they are:

name - no error
surname - 'Exists'

Now, if I change or blur the value of the name field, the whole form validation will be triggered and backend error will be erased for surname field.

How is it possible to erase field’s backend error only if the value of that field was changed?

I can think of possible solution with using form’s status to save backend errors, checking the field’s previous/new value, but it feels like a really big hack.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 46
  • Comments: 32 (15 by maintainers)

Most upvoted comments

workaround ≠ solution This is a rather really common use case and I think Formik could solve this in a much neater way. I think this issue should be reopened.

yep, instead of fixing/adding things let’s just close issues like nothing happened. not even surprised to see this bot here 😄

For anyone else coming to this thread and being none the wiser, I found @jaredpalmer gave an answer on SO that solved my wondering how this is done (in v1).

https://stackoverflow.com/questions/52986962/how-to-properly-use-formiks-seterror-method-react-library

Snippet:

onSubmit={(values, { setSubmitting, setErrors }) => {
                        const axios = getAxiosInstance();

                        axios.post('/reset/', {
                            'email': email
                          }).then((response) => {
                            setSubmitting(false);
                          }).catch((error) => {
                            setSubmitting(false);
                            const fieldErrorsFromResponse = error.response.data.field_errors;

                            // fieldErrorsFromResponse from server has {'field_errors': {'email': 'Invalid'}}
                            if (fieldErrorsFromResponse !== null){
                              setErrors(fieldErrorsFromResponse);
                            }
                          }
                        );
                      }}
                      render={({ isSubmitting, errors }) => (

                        <Form className="form-horizontal">
                          <p>Enter the email address associated with your account, and we'll email you a link to reset your password.</p>
                          <FormGroup>
                            <Label htmlFor="username">Email address</Label>
                            <Field type="email" name="email" />
                            <ErrorMessage name="email" component="div" className="field-error" />
                          </FormGroup>

Feel free to submit PR’s open to exploring this

@jaredpalmer here is a codesandbox demo of what I mean.

Set the values of email and username, submit the form and you’ll receive 2 backend errors. Now, change the value or blur one of the fields (to trigger validation) - all backend errors will be gone.

@vladshcherbin I wish I had time to go through it all but I don’t. So rather than continue to let things pile up, I figured I could make small dent with this bot. If people still need help they will holler (just like you just did!).

This feature is planned in v2 btw #828

I made an example that can make life easier for some people. follow the link more this looks like a hack

import { useCallback } from "react";
import { useField as useFieldFormik, useFormikContext, getIn } from "formik";

export function useField(name) {
  const { status, setStatus } = useFormikContext();
  const [{ onBlur: onBlurFormik, ...field }, meta] = useFieldFormik(name);
  const apiError = getIn(status, name);

  const onBlurMemo = useCallback(
    e => {
      setStatus({
        ...status,
        [name]: null
      });
      onBlurFormik(e);
    },
    [status, name, setStatus, onBlurFormik]
  );

  return [{ onBlur: onBlurMemo, ...field }, { ...meta, apiError }];
}
import React from "react";
import { useField } from "./use-field";

function Input(props) {
  const [field, meta] = useField(props.name);

  return (
    <>
      <input {...field} {...props} />
      {(meta.error || meta.apiError) && (
        <div>{meta.error || meta.apiError}</div>
      )}
    </>
  );
}

export default Input;
import React from "react";
import { Formik } from "formik";
import Input from "./Input";
import { request } from "./helpers";

function FormExample() {
  return (
    <Formik
      validate={values => {
        let errors = {};
        if (!values.email) {
          errors.email = "Required by client";
        }

        if (!values.password) {
          errors.email = "Required by client";
        }

        return errors;
      }}
      initialValues={{ email: "", password: "" }}
      onSubmit={async (values, actions) => {
        try {
          await request();
        } catch (error) {
          actions.setStatus(error);
        }
      }}
    >
      {props => (
        <form onSubmit={props.handleSubmit} autoComplete="off">
          <label htmlFor="email" style={{ display: "block" }}>
            Email
          </label>
          <Input
            id="email"
            name="email"
            placeholder="Enter your email"
            type="text"
            autoComplete="off"
          />

          <label htmlFor="password" style={{ display: "block" }}>
            Password
          </label>
          <Input
            id="password"
            name="password"
            placeholder="Enter your password"
            type="password"
            autoComplete="off"
          />

          <button type="submit">Submit</button>
        </form>
      )}
    </Formik>
  );
}

export default FormExample;

https://codesandbox.io/s/formik-api-errors-9xwjs

Is this possible in v2 now?

@jaredpalmer nah, other libraries can handle this use case.

I’m sure, we’ll find a nice way to deal with this too 😉

@amankkg I had the same problem. I use setStatus to set server side errors, but I wanted those errors to disappear after a given field is changed. Here is my solution:

<Field
  type="email"
  name="email"
  onChange={(v) => {
    if (status && status.email) {
      const { email, ...rest } = status;
      setStatus(rest);
    }
    handleChange(v);
  }}
/>

If this pattern was repeated, you could reuse similar logic in your custom Field component.

Here is what work weel for me. Quite similar to @t-nunes example, but without changing statuses until next submission.

Examples for both versions

@jaredpalmer yes, I transformed backend errors and the fields get them. The problem is that when you change/blur one of fields, all other backend errors are gone since newly triggered validation will erase them.

Anyone know the best way to handle this with sagas? My API is decoupled from the form’s submit function, so I can’t directly access the errors/response in Formik.onSubmit.

My form calls a submit function with the form’s values:

<Formik
  initialValues={{
    email,
    password: "",
    passwordConfirmation: ""
  }}
  validationSchema={ValidationSchema}
  onSubmit={(values, { setErrors, setSubmitting }) => {
    const { password, passwordConfirmation } = values;
    submit(token, password, passwordConfirmation);
  }}
>

submit dispatches an action that starts a request:

const mapDispatchToProps = dispatch => ({
  submit: (token, password, passwordConfirmation) => {
    dispatch(setPasswordRequest({ token, password, passwordConfirmation }));
  }
});

There’s then a “loading”, “success”, and “failure” action that corresponds to setPasswordRequest. The errors and loading state are managed in the Redux store.

But in order to set a form’s errors and loading state with Formik, you need to use setErrors and setSubmitting from the Formik.onSubmit prop.

I could pass setErrors and setSubmitting to setPasswordRequest and call them in the saga, but that seems messy. Anyone know the best way to approach this?

2 options:

  1. Write an small function that transforms your backend error into same shape as formik’s errors and then call setErrors

  2. Use status setStatus