How to set dynamic error messages in Yup async validation?
Asked Answered
S

5

15

I am trying async validation in Formik using Yup's .test() method and need to set the error message that I get from the API. Error messages are going to be different based on some conditions in backend.

Tried few solutions mentioned here
https://github.com/jquense/yup/issues/222 and Dynamic Validation Messages Using Yup and Typescript

But Yup is throwing the default error message given in test().

Documentation says that

All tests must provide a name, an error message and a validation function that must return true or false or a ValidationError. To make a test async return a promise that resolves true or false or a ValidationError.

I am resolving a new ValidationError with the error message but still, it throws the default error.

Here is the code.

const schema = Yup.object().shape({
  email: Yup.string().test(
    "email_async_validation",
    "Email Validation Error", // YUP always throws this error
    value => {
      return new Promise((resolve, reject) => {
        emailValidationApi(value)
          .then(res => {
            const { message } = res.data; // I want this error message to be shown in form.
            resolve(new Yup.ValidationError(message));
          })
          .catch(e => {
            console.log(e);
          });
      });
    }
  )
});
Semibreve answered 17/7, 2020 at 8:35 Comment(3)
Where is resolve coming from? it's an unknown importProfane
it should be available from this so something like this.resolveBrigand
Is it possible to have this for multiple field's error messages...e.g. field 1 with test returning "Field 1 is invalid" and field 2 with test returning "Field 2 is invalid"Wiredraw
S
17

I got it working using the function syntax instead of arrow function for validation function.

Doc says:

test functions are called with a special context, or this value, that exposes some useful metadata and functions. Note that to use this context, the test function must be a function expression (function test(value) {}), not an arrow function, since arrow functions have lexical context.

Here is the working code.

const schema = Yup.object().shape({
  email: Yup.string()
    .email("Not a valid email")
    .required("Required")
    .test("email_async_validation", "Email Validation Error", function (value) { // Use function
      return emailValidationApi(value)
        .then((res) => {
          const message = res;
          console.log("API Response:", message);
          return this.createError({ message: message });
          // return Promise.resolve(this.createError({ message: message })); // This also works
        })
        .catch((e) => {
          console.log(e);
        });
    })
});
Semibreve answered 9/9, 2020 at 9:38 Comment(2)
Note that accessing createError is also possible with arrow syntax, by passing in testContext along with value, then using testContext.createError().Paestum
I'm just curious, the "Email Validation Error" is the message, but how/ what is it used for? Is it just a readability thing, unfortunately I cannot find any documentation on this.Junior
C
4

Actually you're almost correct.You just need to use the following:

resolve(this.createError({ message: message }));

Let me know if it still doesn't work ya

Chaworth answered 3/8, 2020 at 16:5 Comment(1)
Thanks! I had the same issue and what I missed is that it's not enough to just call createError but you also have to return the result of it from your validation function.Overlong
K
3

I am able to do this with arrow function as well.

const schema = Yup.object().shape({
 email: Yup.string()
  .email("Not a valid email")
  .required("Required")
  .test("email_async_validation", "Email Validation Error", (value, {createError}) { 
    return emailValidationApi(value)
      .then((res) => {
        const message = res;
        console.log("API Response:", message);
        return createError({ message: message });
      })
      .catch((e) => {
        console.log(e);
      });
    })
  });
Kerch answered 1/6, 2021 at 10:36 Comment(0)
E
2

Don't pass second parameter, as we generally pass it as a error message instead create your own custom message using "createError" and return it on your condition.

import * as yup from "yup";

const InitiateRefundSchema = yup.object().shape({
  amountPaid: yup.number(),
  refundAmount: yup
    .number()
    .test("test-compare a few values", function (value) {
      let value1 = this.resolve(yup.ref("amountPaid"));
      let value2 = this.resolve(yup.ref("refundAmount"));
      if (value1 < value2) {
        return this.createError({
          message: `refund amount cannot be greater than paid amount '${value1}'`,
          path: "refundAmount", // Fieldname
        });
      } else return true;
    }),
})
Ecclesiastic answered 2/9, 2020 at 9:14 Comment(0)
W
0

With Internatilization ('vue-i18n') and Yup ('yup') options you could use as:

const fullname = yup.string().required(this.$t('validate.required')).min(8, this.$t('validate.min', {min: '8'}))

So with this line below, the text error message will be changed as the locale option change.

this.$t('validate.min')

pt.ts

    validation: {
        required: 'Esse campo é obrigatório',
        size_min: 'Deve no minimo {min} caracteres',
    }

en.ts

    validation: {
        required: 'Required please',
        size_min: 'Please only {min} caracteres',
    }
Withers answered 26/11, 2022 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.