Yup: Show only one error message

Created on 8 Aug 2017  路  8Comments  路  Source: jquense/yup

For my field validation, I have

yup.string()
  .matches(
    /^[A-Z]/,
    {
      message: 'Name must start with a capital letter',
      excludeEmptyString: true,
    },
  )
  .matches(
    /^.[a-zA-Z0-9_]+$/,
    {
      message: 'Alphanumeric characters or underscores only',
      excludeEmptyString: true,
    },
  )
  .required('Name is requried.'),

Sometimes, both errors from the matches(), will show. This makes sense since both errors triggered. However, the way it's displayed can be a bit messy:

Name must start with a capital letter., Alphanumeric characters or underscores only.

Is there any way to only show one message at a time? Or possibly replace the comma with a period?

Most helpful comment

Thanks @jquense, I think this will work (grabbing first error field and using as error):

let errors = [];

try {
    isValid = invitationSchema.validateSync(row, { abortEarly: false });
} catch (e) {
  const nameError = e.inner.find((ve) => ve.path === 'name')
    ? { name: e.inner.find((ve) => ve.path === 'name').message }
    : null;

  errors.push({
    ...nameError,
  });

  // result: { name: 'First message for name' }
}

All 8 comments

I noticed there was the abortEarly option, but it's not clear to me from the docs how to use that in the above chain of functions.

What context are you showing messages? React-formal? abortEarly is an option can pass to schema.validate, but not something you can control directly in RF, there you should look at the Message component api, it has toggles for displaying messages differently

Since this appears to have died down, I'll post another example (I believe it's the same question). This is straight from JS, not using react-formal.

const invitationSchema = Yup.object().shape({
  name: Yup.string()
    .min(3, 'Minimum of 3 characters')
    .required('Name is required'),
  email: Yup.string()
    .email('Valid email required')
    .required('Email is required'),
});

const row = {
  name: '',
  email: '',
};

const isValid = invitationSchema.validateSync(row, { abortEarly: false });

Result

  errors: Array(3)
    0: "Minimum of 3 characters"
    1: "Name is required"
    2: "Email is required"
  ...
  message: "3 errors occurred
  ...

Expected

However, I would expect it to only return one of the name validation errors, not both of them; however, I do want both the name and email errors to show (but again, only one of each). Setting abortEarly to true is necessary to ensure that both field validations run; however, I only want one of each field errors.

@kendallroth Validations for fields are run in parallel, which means the abortEarly is an all or nothing action. If you only want one error per field why not just take the first error per field? It's also not clear how that would work if not like abortEarly works now in the case of nested objects.

@jquense Understood. I just double checked and it appears that changing the order of the chained name validation methods will change the order in which the errors appear. For instance, switching to Yup.string().required().min() will display the required message before the min messages, whereas Yup.string().min().required() will display it afterward.

Or is this just a lucky coincidence because the first one just happaned to start before the other? Also, if validateSync is synchronous, do the validations still run in parallel?

Or is this just a lucky coincidence

I wouldn't say coincidence, the validations you have here are all otherwise synchronous (but wrapped in a promise). So the promises are all always the same "length" in time, but that they resolve in that order is not defined behavior for Promises, it's just sort of what happens naturally.

Also, if validateSync is synchronous, do the validations still run in parallel?

In that case no.

Thanks @jquense, I think this will work (grabbing first error field and using as error):

let errors = [];

try {
    isValid = invitationSchema.validateSync(row, { abortEarly: false });
} catch (e) {
  const nameError = e.inner.find((ve) => ve.path === 'name')
    ? { name: e.inner.find((ve) => ve.path === 'name').message }
    : null;

  errors.push({
    ...nameError,
  });

  // result: { name: 'First message for name' }
}

I can be sure this is a problem.
such as

Yup.string().required().min()

Since there is no parameter, it is better to trigger the required directly. Why does it trigger min?

Was this page helpful?
0 / 5 - 0 ratings