dimitry_n
dimitry_n

Reputation: 3019

Showing errors returned via API - React Final Form

I am trying to display inline errors returned via API, for each invalid field. I am following the Submission Errors example from here with something like this:

const handleSubmit = async (values) => {
  let errors;

  await createProduct(values) // axios.post(...)
    .then((res) => ...)
    .catch((error) => {
      errors = error.response.data.errors;
      console.log(errors); // returns { title: ["can't be blank"] }
    });

  return errors; // returns { title: ["can't be blank"] }
}

then my form looks like this:

   <Form onSubmit={handleSubmit}>
        {({
          submitError,
          handleSubmit,
          ...
        }) => (
          <BSForm onSubmit={handleSubmit}> // BSForm for boostrapform
            {submitError && (
              <div className="error">{submitError}</div> // not showing
            )}
            {console.log(submitError)} // returns 'undefined'
            <TextField
              name="title"
              ...
            />

and I am only able to invoke submitError if I pass {[FORM_ERROR]: 'error message'} instead of returning the errors obj.

I'd love to be able to just relay these API errors into corresponding fields' meta: { error } props, but I can totally live with submitError wrapping API errors.

Upvotes: 0

Views: 4915

Answers (2)

Chaiwa
Chaiwa

Reputation: 103

Here is what worked for my use case! In the submit handler, I just needed to resolve the promise with the error object sent by the api, then React-final-form(RFF) would propagate the error messages down to the individual field states via the meta.submitError.

The other thing to note here is that, the error object that gets resolved should contain matching object keys or property names as the ones sent to the server(i.e. field names in form.values should match the object keys you resolve back when you receive a submit error). My submit handler finally looked like this:

 const submit = (values) => {
    const promise = 'some promise or api call';

    return new Promise(resolve => {
       promise(values))
        .then(response => {
          // ...do whatever
          resolve(true);
        })
        .catch(error =>{
             if (error.validationErrors) {
                resolve({ [FORM_ERROR]: error.validationErrors });
             } else {
                 //Depending on the shape of the error object from the server, destructure it or transform it so the keys match the field names in the form
                 //Example submissionErrors object would look like: {name: "Duplicate name detected", age: "Invalid age"}
                  const submissionErrors = {
                    ...error.data?.errors,
                    ...error.response?.data?.errors,
                   }; 
               // Then finally `resolve` the errorObject back to the form. 
              //This `resolve` is what makes RFF to update the fieldState of the affected fields with `meta.submitError`
             // You can then use `meta.submitError`, from `fieldState` of a given field, to display its `submission error`
               resolve({ ...submissionErrors});
           }
       });
    });
  };

Upvotes: 2

dimitry_n
dimitry_n

Reputation: 3019

Found the solution 30 seconds after posting. I missed the meta: { submissionError } prop in the example.

So the way I am returning errors seems to be correct, the only difference is instead of displaying Field level errors with meta.error && meta.touched, I have to add meta.submitError like so:

(meta.error || meta.submitError) && meta.touched; 

then to display the error:

{(meta.error || meta.submitError) && meta.touched && (
  <span>{meta.error || meta.submitError}</span>
)}

Upvotes: 1

Related Questions