Open BennieVE opened 1 year ago
Affecting me as well.
I had to write a custom forEach to handle cases where items are not objects.
For the record, the code below is my own helper, based on vuelidate's own. It can probably be optimised, but I needed something fast to be able to move over to the new version.
/*
* https://github.com/vuelidate/vuelidate/blob/next/packages/validators/src/utils/forEach.js
*/
import { computed } from 'vue'
import { helpers } from '@vuelidate/validators'
export default function forEach (validators, forceSimple = false) {
return {
$validator (collection, ...others) {
return helpers.unwrap(collection).reduce(
(previous, collectionItem, index) => {
if (!forceSimple && typeof collectionItem === 'object') {
const collectionEntryResult = objectForEach(collectionItem, index, validators, others)
return {
$valid: previous.$valid && collectionEntryResult.$valid,
$data: previous.$data.concat(collectionEntryResult.$data),
$errors: previous.$errors.concat(collectionEntryResult.$errors)
}
}
const propertyResult = primitiveForEach(collectionItem, index, validators, others)
return {
$valid: previous.$valid && propertyResult.$valid,
$data: previous.$data.concat(propertyResult.$data),
$errors: previous.$errors.concat(propertyResult.$errors)
}
},
{ $valid: true, $data: [], $errors: [] }
)
},
$message: ({ $response }) => {
if (!$response) return []
return $response.$errors.map(context => {
if (context.$model) return context.$message
return Object.values(context)
.map(errors => (Array.isArray(errors) ? errors.map(e => e.$message) : errors.$message))
.reduce((a, b) => a.concat(b), [])
})
},
$params: computed(() => {
return Object.entries(validators).reduce((map, [key, value]) => {
map[key] = { ...value.$params }
return map
}, {})
})
}
}
function primitiveForEach ($model, index, validators, others) {
const innerValidators = validators || {}
return Object.entries(innerValidators).reduce(
(all, [validatorName, currentValidator]) => {
const validatorFunction = helpers.unwrapNormalizedValidator(currentValidator)
const $response = validatorFunction.call(this, $model, index, ...others)
const $valid = helpers.unwrapValidatorResponse($response)
all.$data[validatorName] = $response
all.$data.$invalid = !$valid || !!all.$data.$invalid
all.$data.$error = all.$data.$invalid
if (!$valid) {
let $message = currentValidator.$message || ''
const $params = currentValidator.$params || {}
if (typeof $message === 'function') {
$message = $message({
$pending: false,
$invalid: !$valid,
$params,
$model,
$response
})
}
all.$errors.push({
$message,
$params,
$response,
$model,
$pending: false,
$validator: validatorName
})
}
return {
$valid: all.$valid && $valid,
$data: all.$data,
$errors: all.$errors
}
},
{ $valid: true, $data: {}, $errors: [] }
)
}
function objectForEach (collectionItem, index, validators, others) {
return Object.entries(collectionItem).reduce(
(all, [property, $model]) => {
const innerValidators = validators[property] || {}
const propertyResult = Object.entries(innerValidators).reduce(
// eslint-disable-next-line no-shadow
(all, [validatorName, currentValidator]) => {
const validatorFunction = helpers.unwrapNormalizedValidator(currentValidator)
const $response = validatorFunction.call(this, $model, collectionItem, index, ...others)
const $valid = helpers.unwrapValidatorResponse($response)
all.$data[validatorName] = $response
all.$data.$invalid = !$valid || !!all.$data.$invalid
all.$data.$error = all.$data.$invalid
if (!$valid) {
let $message = currentValidator.$message || ''
const $params = currentValidator.$params || {}
if (typeof $message === 'function') {
$message = $message({
$pending: false,
$invalid: !$valid,
$params,
$model,
$response
})
}
all.$errors.push({
$property: property,
$message,
$params,
$response,
$model,
$pending: false,
$validator: validatorName
})
}
return {
$valid: all.$valid && $valid,
$data: all.$data,
$errors: all.$errors
}
},
{ $valid: true, $data: {}, $errors: [] }
)
all.$data[property] = propertyResult.$data
all.$errors[property] = propertyResult.$errors
return {
$valid: all.$valid && propertyResult.$valid,
$data: all.$data,
$errors: all.$errors
}
},
{ $valid: true, $data: {}, $errors: {} }
)
}
Affecting me as well.
I had to write a custom forEach to handle cases where items are not objects.
For the record, the code below is my own helper, based on vuelidate's own. It can probably be optimised, but I needed something fast to be able to move over to the new version.
/* * https://github.com/vuelidate/vuelidate/blob/next/packages/validators/src/utils/forEach.js */ import { computed } from 'vue' import { helpers } from '@vuelidate/validators' export default function forEach (validators, forceSimple = false) { return { $validator (collection, ...others) { return helpers.unwrap(collection).reduce( (previous, collectionItem, index) => { if (!forceSimple && typeof collectionItem === 'object') { const collectionEntryResult = objectForEach(collectionItem, index, validators, others) return { $valid: previous.$valid && collectionEntryResult.$valid, $data: previous.$data.concat(collectionEntryResult.$data), $errors: previous.$errors.concat(collectionEntryResult.$errors) } } const propertyResult = primitiveForEach(collectionItem, index, validators, others) return { $valid: previous.$valid && propertyResult.$valid, $data: previous.$data.concat(propertyResult.$data), $errors: previous.$errors.concat(propertyResult.$errors) } }, { $valid: true, $data: [], $errors: [] } ) }, $message: ({ $response }) => { if (!$response) return [] return $response.$errors.map(context => { if (context.$model) return context.$message return Object.values(context) .map(errors => (Array.isArray(errors) ? errors.map(e => e.$message) : errors.$message)) .reduce((a, b) => a.concat(b), []) }) }, $params: computed(() => { return Object.entries(validators).reduce((map, [key, value]) => { map[key] = { ...value.$params } return map }, {}) }) } } function primitiveForEach ($model, index, validators, others) { const innerValidators = validators || {} return Object.entries(innerValidators).reduce( (all, [validatorName, currentValidator]) => { const validatorFunction = helpers.unwrapNormalizedValidator(currentValidator) const $response = validatorFunction.call(this, $model, index, ...others) const $valid = helpers.unwrapValidatorResponse($response) all.$data[validatorName] = $response all.$data.$invalid = !$valid || !!all.$data.$invalid all.$data.$error = all.$data.$invalid if (!$valid) { let $message = currentValidator.$message || '' const $params = currentValidator.$params || {} if (typeof $message === 'function') { $message = $message({ $pending: false, $invalid: !$valid, $params, $model, $response }) } all.$errors.push({ $message, $params, $response, $model, $pending: false, $validator: validatorName }) } return { $valid: all.$valid && $valid, $data: all.$data, $errors: all.$errors } }, { $valid: true, $data: {}, $errors: [] } ) } function objectForEach (collectionItem, index, validators, others) { return Object.entries(collectionItem).reduce( (all, [property, $model]) => { const innerValidators = validators[property] || {} const propertyResult = Object.entries(innerValidators).reduce( // eslint-disable-next-line no-shadow (all, [validatorName, currentValidator]) => { const validatorFunction = helpers.unwrapNormalizedValidator(currentValidator) const $response = validatorFunction.call(this, $model, collectionItem, index, ...others) const $valid = helpers.unwrapValidatorResponse($response) all.$data[validatorName] = $response all.$data.$invalid = !$valid || !!all.$data.$invalid all.$data.$error = all.$data.$invalid if (!$valid) { let $message = currentValidator.$message || '' const $params = currentValidator.$params || {} if (typeof $message === 'function') { $message = $message({ $pending: false, $invalid: !$valid, $params, $model, $response }) } all.$errors.push({ $property: property, $message, $params, $response, $model, $pending: false, $validator: validatorName }) } return { $valid: all.$valid && $valid, $data: all.$data, $errors: all.$errors } }, { $valid: true, $data: {}, $errors: [] } ) all.$data[property] = propertyResult.$data all.$errors[property] = propertyResult.$errors return { $valid: all.$valid && propertyResult.$valid, $data: all.$data, $errors: all.$errors } }, { $valid: true, $data: {}, $errors: {} } ) }
I'm Confused about this answer, using a librairy is suppose to reduce and facilitate the work and absorb the complicated part of implementing functionality.
I thinks i wil find another librairy making this kind of job. 🤔
Well, that code already exists, I just split it into two different parts, with small modifications. It is code already available in the library.
I still have this problem, any helps?
forEach
helper is for an array of objects, should use Array.every()
with custom validator (see here)?
Like this?
myEmails: {
isEmails: (vals) => vals.every(v => email.$validator(v)),
},
Documentation isn't clear on it
@wadclapp in my case , I used v-for and an array to bind v-model. Instead of knowing error, I need to know which index in the array causes the error and show it. So using Array.every() did not solve my problem and that why I used $each.
If you need index when validating an array you could do this (as suggested in same issue linked above)?
function validateEach(vals, validationObj) {
return vals.reduce((accVal, currVal, index) => {
accVal[index] = validationObj
return accVal
}, {})
}
validations() {
return {
emails: validateEach(this.emails, {
email,
}),
}
}
Output:
{
...
"$invalid": false,
...
"emails": {
"0": {
...
"$invalid": false,
...
},
"1": {
@tp27933
The replacement I have published above should work for your use case. You don't have to use it all the time, just when you need primitive support on forEach. It has been working well for my use case.
@wadclapp thanks , but which field should I use to know is there an error? $error or $invaild? @nolde don't quite understand your answer above. which version of the library cover the code? I already update it to latest version ^2.0.2, but still not working
@tp27933
Vuelidate has added a new helper called forEach
that works with arrays of objects. Unfortunately, it does not work properly with arrays of primitives, such as strings.
My code above is a modification of the original forEach
helper to add support to primitives. It has been working well for my use case.
Just drop it in a file on your project, import it and use it in place of the original one. It should still support object as well.
Describe the bug
$each
is responsible for evaluating collections. The new documentation says to use thehelpers.forEach
. The example in the docs uses an array of objects, but when I try to apply it to non-objects such as strings / numbers it does not work.Reproduction URL The sandbox tries to evaluate the array of strings, but it doesn't pick up that anything is wrong.
Vuelidate 2 - Options API + Vue 3
To Reproduce Steps to reproduce the behavior:
Expected behavior The array should be invalid.
Additional context Regarding the code sandbox. i know i can validate the email before it gets added.