redux-form: Async validation failing causes Uncaught promise error

The link in the docs to rc.3 is broken but you can see this in rc.2

Go to http://redux-form.com/6.0.0-rc.2/examples/asyncValidation/ and fail the validation. You will get a undefined:1 Uncaught (in promise) Object {username: "That username is taken"} error

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 6
  • Comments: 54 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Solved it, make sure that you are returning the promise in your handleSubmit. I had it fetch(... whereas it should be return fetch(...

This is how async validation works in practise right now:

  1. If you have errors just return an object with the fields.
  2. If no errors then return null (null is nice when “nothing” is an intended result)

Example:

function asyncValidate(values) {
    // Assume errors is already in Redux Form compatible format.
    // Here catch essentially just ignores error if:
    // 1) destructuring errors failed in .then
    // 2) fetchStuff threw an error, for example if you have custom logic in case fetch() response.ok is false
    return fetchStuff(values).then(
        ({ errors }) => errors || null
    ).catch(() => null)
}

Essentially: the docs are incorrect. You never need to throw.

The major “flaw” in the current design is that rejecting is not really handled properly, which is the bug you see. And in general when managing errors in promises you should always throw new Error(reason) as at least I’ve come to expect to have an Error instance when doing .catch(). It gets mentally confusing to manage errors if you can’t know what to expect!

I think the Uncaught “bug” doesn’t need to be fixed as people can simply change their code to not to throw, instead the docs need to be fixed so that they match the actual usage as the current example is confusing.

To correct the issue with promise usage there are a couple of hard questions:

  1. Should validation only fail if the promise is rejected? (To match usage of stuff like SubmissionError.)
  2. Should there be a custom error object like AsyncValidationError?
  3. Should validation be able to distinguish between warning level and error level errors? (there seems to exist input.warning in props but I have never used it yet; have not yet researched what the warning thing is for in redux-form’s case)

Related to this I happen to have a case where I can get error and/or warning level validation errors: warning level can be ignored but error level cannot. The current asyncValidate doesn’t really support this use case so I have to “manually hack” with component state.

It would be nice if the async validation and it’s usage of promises and errors is rethought, and then changed if seen worthwhile.

…and can be reproduced on http://redux-form.com/6.5.0/examples/asyncValidation/

Just enter something in the password field and then type “paul” in the user field. Then, while still having the user field focused (meaning that validation has not yet kicked in), klick on the submit button.

The validation error will show as expected, but there is also a unhandled Promise rejection in the console.

image

Here is a working one with version6.5.0

TL;DR return the error the result of the promise, which is an error

in the render() function of the form component I have this line: <form onSubmit={handleSubmit(this.myCustomSubmit.bind(this))}> (no surprises here)

myCustomSubmit(values) {

       // This is the !!!TRICK!!! you need to return the error raised inside the promise
       return this.props.createAction(values)
            .then(resp => {

                // If there is an error in the response, throw a submission error
                if (resp.error) {
                    throw new SubmissionError(resp.payload.response.data);
                } else {
                    this.props.submitFromParentComponent();
                }
            });

Note This was working perfectly for me, but then I factored out this code into a separate function like which needs to be return-ed as well:

myCustomSubmit(values) {
    return doAnythingYouWant(values);
}

and in doAnythingYouWant() the exception will be thrown in the promise.

Ps. I am using axios@0.15.3 not that it would matter

Having this issue with v6.0.2…

Seems to come from here:

var handleErrors = function handleErrors(rejected) {
    return function (errors) {
      if (errors && Object.keys(errors).length) {
        stop(errors);
        return Promise.reject(errors);
      } else if (rejected) {
        stop();
        throw new Error('Asynchronous validation promise was rejected without errors.');
      }
      stop();
      return Promise.resolve();
    };
  };

Once you got the errors and know the promise was rejected, why are you rejecting again? You’re at the end of the promise chain so you should just swallow up the error.

I would remove this line: return Promise.reject(errors);

As a solution you can use such kind of hack:

render() {
  const { handleSubmit } = props;

  const onSubmit = e => {
    e.preventDefault();
    handleSubmit().catch(() => null);
  };

  return (
    <form onSubmit={onSubmit}>
       ...

Also I’ve created a Pull Request to fix the bug https://github.com/erikras/redux-form/pull/3227 Will waiting for the review from authors.

In case anyone else has the same silly mistake. As outlined in https://github.com/erikras/redux-form/issues/2269#issuecomment-291214790, if using redux-form/immutable, make sure to also import SubmissionError from there:

import { SubmissionError } from 'redux-form/immutable'

Now it works even with async functions:

export const submit = (id) => async (data) => {
  try {
     await sendForm(id, data)
  } catch (e) {
    if (e.code && e.code === 'memberExists') {
      throw new SubmissionError({ _error : 'You have already signed up!' })
    } else {
      throw new SubmissionError({ _error : 'An unexpected error occurred. Please try again later.' })
    }
  }
}

Facing the same problem here using redux-form@6.5.0 Can be reproduced when the form fulfills the following conditions:

  1. The form is using validate, asyncValidate, initialValues in the config.
  2. asyncValidate can not be triggered by any asyncBlurFields.
  3. At least one field has to be touched.
  4. The form data at the end of step 3 will pass the validate rules.
  5. Trigger submission.

Reproducing with the “Async Validation” demo, add the following to the reduxForm config: initialValues: { username: 'john' }

Input anything in the password field that will pass the validate. Submit.

screen shot 2017-03-03 at 9 58 26 am

Unfortunately v6.0.3 doesn’t help. Still get uncaught exception in console

Any ETA for when this will be published?

Confirming that the issue still exists on v6.0.5. I would like to add that it is easily reproducible using the async demo from the official website. Simply type ‘george’ and see it happen. I hope that reproducing it makes it easier to fix.

This expected to be fixed in 7.0.3 🎉. Is someone who was affected by this willing to confirm?

I still got the error in 7.0.3 and I did like described in docs for an exchange of a warn (removed the SubmissionError):

Broken way: throw new SubmissionError({ email: 'That email is taken' })

Works but gave me warn of “Expected an object to be thrown”: throw { email: 'That email is taken' }

This code is inside an Axios request which validates if the email is registered.

The problem is present in v7.0 again. Upgrading to this version produced the error. And after many hours of unsuccessfull attempts, tried to downgrading redux-form to v6.5, and async validation started to work as intended.

This definitely needs better documentation

Any update to this issue? I an experiencing this issue with just a regular submit. Just submit the form, where onSubmit returns a promise. I suspect it is the same issue - because I get the same results shown here.

In my situation If I have this, I do NOT see the uncaught promise

            .then(data => {
                if (!data.isValid)
                    throw new SubmissionError(data.errors);
            })
            .catch(err => {
                if (err instanceof SubmissionError)
                    throw err;
            });

But if I embed another promise inside and throw from there - I do see it.

const someHelper = (response) => {
     throw new SubmissionError(data.errors);
    return response;    
}

            .then(someHelper)
            .catch(err => {
                if (err instanceof SubmissionError)
                    throw err;
            });

@Merri ‘returning’ errors does not really work for the situation above, because they are inside ‘then’ clauses and the calling code has no way to know what to return. A throw (or reject) is really needed as returning the SubmissionError or err.errors still causes the issue. (and it has to be thrown as it is deep in chain)

const someHelper = (response) => {
     throw new SubmissionError(data.errors);
    return response;    
}

            .then(someHelper)
            .catch(err => {
                if (err instanceof SubmissionError)
                    return err;
            });

It looks like it may have been fixed in a prior version? Any ideas on the throwing of the exception from a ‘contained’ promise? Works fine if from the top level promise.

Here is an example of asyncValidation in redux-form 7.2.1: https://codesandbox.io/s/8on6qk15j

@erikras can you check it, please?

Re-opening as this issue appears still active.

@sebmor PRs are welcome

I use this in meteor @irisSchaffer I don’t have to use

import { SubmissionError } from 'redux-form/immutable'

just

import { SubmissionError } from 'redux-form'

is fine.

Here is my solution


export const asyncValidate = values => {
 
  return new Promise((resolve, reject) => {
      reject({ yourFieldName: 'your error message' })
    })
}

credit: the idea from this issue https://github.com/erikras/redux-form/issues/2021 Best,

I managed to kind of solve the problem. This solution will work for the ones using axios.

 const asyncValidate = values => {
  const {username} = values
    const response = axios.post(<url>, {username}, {validateStatus: function (status) {
    return status < 500; // Reject only if the status code is greater than or equal to 500
  }})
    return response.then(response => {if(response.status == 400){
      throw {username: 'That username is already taken'}
    }})
};

validateStatus config will enable axios to receive error codes as part of response rather than error. In my case server returns 400 BAD REQUEST when username already exists. I assume the problem is with the promise.catch().

Still the same. It may even be reproduced with this demo screenshot_2016-08-17_00-48-33