logaretm / vee-validate

✅ Painless Vue forms
https://vee-validate.logaretm.com/v4
MIT License
10.8k stars 1.26k forks source link

Mapping errors to VeeValidate #1254

Closed muscle-hamster closed 6 years ago

muscle-hamster commented 6 years ago

Description:

I have searched for an answer to this and have not found anything that suits my needs, so I apologize if this has already been answered. With that being said, I am using a custom middleware that I wrote for my server side validation, and I am using VeeValidate for my front end validation. All my errors from my server side validation get returned in an object and I was wondering if there was a way to map the errors from my middleware to their respective field where the error occured using VeeValidate? Perhaps using the name of the field?

I understand this is a bit redundant, but I am wanting to do it just in case both forms of validation get out of sync as there are a lot of people touching this particular code. All I'm looking to do is change the state to 'dirty' and display a generic error message.

Hopefully this makes sense and thank you for such a great tool!

logaretm commented 6 years ago

I guess not having a full example may lead to confusion about this topic, so let me try to explain a complete way of doing it:

error.add adds an error object to the error bag internal array, an error object looks like this:

{
  "id": "The field Id",
  "field": "The field name",
  "scope": "The field scope",
  "regenerate": "Function that generates the same message in the currently activated locale",
  "msg": "The actual string for the message",
  "rule": "The name of the rule that generated this error"
}

If you are doing the validation using the directive, these fields will be filled out for you but when you are syncing the error bag with an external resource you would need to provide them yourself, at least the field, id, msg fields.

Assuming my endpoint returns the errors to look something like Laravel's response:

{
  "errors": {
    "firstName": ["Array of errors"],
    "lastName": ["Another One"]
  }
}

The only issue here is to map those errors and fill the missing information, let us start with the easy ones, we have from this response, the field name, and the msg property.

// map it to pusdo error object, we still need the id.
const firstNameErrors = response.data.errors.firstName.map(msg => {
  return { field: 'firstName', msg };
});

the fields collection has a handy API to find fields, using an object as a matcher like this:

const field = this.$validator.fields.find({ name: 'firstName' }); // get me the first field that has this name.

Now set the id, and add the errors.

firstNameErrors.forEach(error => {
  // we can access the id using field.id
  error.id = field.id;
  this.errors.add(error);
});

finally update the flags using the setFlags method which sets the provided flags along with their opposite ones, meaning if you set valid it sets invalid automatically and vice versa, the same thing for dirty and pristine.

field.setFlags({
  valid: !! firstNameErrors.length,
  dirty: true
});

Now, this seems like a lot of work, but wrapping it in a function could make more reusable for your use case. This is how the pipeline mostly works internally as well and this implementation should work the same without sync issues. Let me know if its not the case.

pbowyer commented 5 years ago

Thanks for this writeup, it was exactly what I needed to get my server-side validation showing next to the correct fields.

I think the setFlags() call needs to be:

field.setFlags({
  valid: !!! firstNameErrors.length,
  dirty: true
});

Otherwise valid was set to `true, which when there's an error seemed wrong.