Redux-form: Async validation failing causes Uncaught promise error

Created on 7 Jul 2016  路  54Comments  路  Source: redux-form/redux-form

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

Most helpful comment

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

All 54 comments

Same error for me using axios.

Also seeing this.

Would really like for the error to be passed down to the Form through props. There is an error property however it is undefined, the propsinvalid/valid both work respectively when validation fails. Also, it seems like a prop type of asyncErrors is supposed to exist, however not seeing it. Most likely a separate issue than the one above.

^^ solved the error prop issue (the payload wants a _error key/value):
throw { email: 'That email is registered already', _error: 'email-exists' };

This problem is still visible in rc.4, any update on that? Maybe some workaround?

I've tried same example for 5.3.1 version (without any strange errors) http://redux-form.com/5.3.1/#/examples/asynchronous-blur-validation?_k=cbebna maybe it will give some clues how to fix this i v6.

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

Due to inspecting devtools console seems to be that this issue wasn't fixed in rc.5 either :(

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

@Strato could you make a PR please :)

Potential fix published in v6.0.3. Please confirm.

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

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.

Yayy! Finally! Waiting for new release )

Any ETA for when this will be published?

Published in v6.1.0.

Having same problem in v6.2.0

same in 6.3.2

Was anyone able to find a solution for this?

Same problem in 6.3.2

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

Thanks @Anarios. For the benefit of anyone else, I did the throw new SubmissionError in my .then block (axios), and then my catch block became:

.catch(function (error) { throw error; }

This is still unfixed in 6.5.0

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

I've made an interesting find. I have the same problem and have a way to avoid it now.

function asyncValidateRegisterInput(values) {
  const { username, email, displayName } = values;
  const user = { username, email, displayName };
  return authClient.validateRegisterData(user).catch((err) => {
    const errors = {};
    err.messages.forEach((item) => {
      errors[item.type] = item.message;
    });
    throw errors;
  })
}

This produces the error Uncought in Promise even if the underlying Promise resolves (but returns a value).

If I change this to:

function asyncValidateRegisterInput(values) {
  const { username, email, displayName } = values;
  const user = { username, email, displayName };
  return authClient.validateRegisterData(user).catch((err) => {
    const errors = {};
    err.messages.forEach((item) => {
      errors[item.type] = item.message;
    });
    throw errors;
  }).then(() => Promise.resolve());
}

This gets rid of the error.

Apparently returning ANYTHING from the resolved promise results in it being treated as an error. Instead of just treating rejection as an error.

I've tested this by changing Promise.resolve()to Promise.resolve("test") and was able to get the error again.

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:
js 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.

@Merri, thanks so much! This helps me to get async validation working.

I noticed that asyncValidation is not being run when submitting a form that has none of it's fields touched (ie. when I hit submit on a form that has just appeared). However, once one of the fields has been "touched", then asyncValidate is called every time I hit submit. Is that normal / intended?

Looking at tests it seems like running async validation on submit is intended behavior. Not doing async validation on untouched form seems sensible since you probably always have a required field which would block sending empty form anyway and you wouldn't get to submitting.

But personally I'm not sure if async validation should run before every submit; to me it would seem like the job of the final actual submit to throw validation errors. It kind of seems like duplicate work when both async validator and full form validator execute, especially as it is likely both do mostly the same validations on server side.

I'm not even using async validation and still getting this error.

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 [email protected] not that it would matter

Facing the same problem here using [email protected]
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

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().

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.

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.' })
    }
  }
}

@irisSchaffer i ran into this, too... for subsequent readers, it's worth calling out that ReduxForm doesn't really play well with submitting anything outside of the context of its own components, so i'm ending up (like you) using a higher order function to pass initial values in and then let the form call the returned function and retain access to those values through the closure.

kinda funky... i want to dig into this and figure out if there's a more robust or stabler way to use these guys.

I was getting this error even on submit validation (like @waynebrantley) and was racking my brains for couple of days. I found that SubmissionError I was throwing even was not instanceof SubmissionError...

It seems that the reason was how I imported SubmissionError:

import SubmissionError from 'redux-form/lib/SubmissionError';

When I changed it to:

import { SubmissionError } from 'redux-form';

it started to work as described in documentation.

I have no problem throwing error but how to return error message back to UI?

I use the same function as my submitValidate which works fine and throw the error to UI but with asyncValidate it only throw error in console log but not in UI

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,

This definitely needs better documentation

@sebmor PRs are welcome

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.

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.

Re-opening as this issue appears still active.

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

Yep! Updated redux-form to v7.0.3 and it is working without any problem!

Also can confirm. The error doesn't appear in 7.0.3

Thanks everyone!

If you still have the error, make sure you actually return a Promise in your submit function:

const workingSubmit = (values) => {
  return Promise.reject(new SubmissionError({_error: 'test'}))
}

const nonWorkingSubmit = (values) => {
  Promise.reject(new SubmissionError({_error: 'test'}))
}

The latter will show an uncaught Promise.

i try to trigger asyncValidate in the form's onChange, it still have the error. 7.0.3

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.

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

@erikras can you check it, please?

+1

I'm having the same issue when trying to submit via ActionCreator. So instead of clicking submit button, I'm dispatching submit action, which cause exactly the same error.

https://codesandbox.io/s/mq94q867p8

+1

I'm having the same issue when trying to submit via ActionCreator. So instead of clicking submit button, I'm dispatching submit action, which cause exactly the same error.

https://codesandbox.io/s/mq94q867p8

I came across the same issue in V 8.1.0. I solved it by writing the AXIOS api call using Async/Await.
This is the example for the asyncValidate.js File -

import axios from 'axios';

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const asyncValidate = (values /* , dispatch */) => sleep(1000).then(async () => { // simulate server latency
await axios.get('https://jsonplaceholder.typicode.com/users').then((res) => {
console.log('Response from JSONPlaceholder', res);
console.log('THE VALUES OBJECT CONTAINS EMAIL as', values.email);
if (values.email === res.data[0].email) {
// eslint-disable-next-line no-throw-literal
throw { email: '' };
} else {
// eslint-disable-next-line no-throw-literal
throw { email: 'Email does not exist' };
}
});
});

export default asyncValidate;

I'm having the same issue, in version: 7.4.2, has someone some of the previous suggested fixes won't work for me 1 or 2, 3, 4
Does anyone has any news on this?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rob-mcgrail picture rob-mcgrail  路  3Comments

ashwinvandijk picture ashwinvandijk  路  3Comments

wtfil picture wtfil  路  3Comments

tylergoodman picture tylergoodman  路  3Comments

tejans24 picture tejans24  路  3Comments