Vue access errors inside computed - vue.js

I'm just starting to learn Vue and am on to validation.
I've found some older examples which use Vee-Validate but it seems to have changed recently. How can I convert this code to use the new version of Vee-Validate?
As far as I can tell, the code below is attempting to send a bespoke error message rather than the default to the screen if there is an error.
Chrome browser is telling me that it cannot read property 'first' of undefined, so I don't think I can access the error using this.errors.
Is it still possible to access the errors inside 'computed'?
<template>
<div>
<ValidationProvider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ myError }}</span>
</ValidationProvider>
</div>
</template>
<script>
import { ValidationProvider } from 'vee-validate';
export default {
components: {
ValidationProvider
},
computed: {
myError () {
if (this.errors.first('myfield') === 'The myfield field is required.') {
return 'My bespoke message'
}
return this.errors.first('myfield')
}
}
};
</script>

As answered by MartinT, ValidationProvider uses Scoped Slots and therefore you cannot access it directly in the parent component. However, you still have some options to achieve what you want:
Using Methods
You can pass the error array as an argument of a method and return the bespoke error message you want!
<template lang="html">
<validation-provider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ myError(errors) }}</span>
</validation-provider>
</template>
<script lang="js">
import { ValidationProvider } from 'vee-validate'
export default {
components: { ValidationProvider },
data () {
return {
input: null
}
},
methods: {
myError (errors) {
if (errors[0] === 'The myfield field is required.') {
return 'My bespoke message'
}
return errors[0]
}
}
}
</script>
But this isn't probably the best option.
Customizing Rules' Messages
In Vee-Validate 3.x.x you can easily customize the error message from an existing rule or you can even create a new rule.
import { extend } from 'vee-validate'
// overwriting the 'required' rule error message.
extend('required', {
...required
message: 'My bespoke message'
})
And then you can display the bespoke message.
<template lang="html">
<validation-provider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ errors[0] }}</span>
</validation-provider>
</template>

Have a look at Scoped slots. In a nutshell, ValidationProvider component is using a slot, which provides you with errors object. You can think of it as an internal object of ValidationProvider. However, the problem is, it can only be used inside of the slot scope (within ValidationProvider). Your usage assumes, that errors obj is part of your component instance (either data, computed, method...), which is not true. More reading can be found here.

Related

Why do I need the v-bind when I have the v-on?

In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.

passing object to component using v-for

I am trying to send a series of objects that are in an array to a child component using v-for, but when I try to access them from the child component, it tells me that the props are not defined.
Im using Quasar Framework actually
This is how I pass the data:
<div class="row justify-center">
<foo
v-for="brand in brands"
:key="brand.id"
:brand="brand"
></foo>
</div>
<script>
import foo from "src/components/foo.vue";
export default {
components: {
foo
},
data() {
return {
brands: []
};
},
methods: {
async getData() {
let x = await get.getData();
this.brands = x.data;
console.log(this.brands);
}
},
mounted() {
this.getData();
}
};
</script>
brands is an array that obtains two objects from a request made to a local database, which I have already verified that it receives the data correctly
And this is the component file and how I try to get the properties:
<q-card class="my-card" flat bordered>
<q-img
:src="require(`../assets/${brand.img}`)"
:alt="brand.img + ' Logo'"
/>
<div class="text-h5 q-mt-sm q-mb-xs">{{ brand.name }}</div>
<div class="text-caption text-grey">
<p>
{{ brand.price }}
</p>
</div>
<script>
export default {
name: "foo",
props: ["brand"],
data() {
return {
expanded: false
};
},
};
</script>
but when I try to execute the code it gives me the following error:
Error in render: "Error: Cannot find module './undefined'
I know one way to make it work, and it is by creating a property for each of the object's values, for example:
<component
v-for="brand in brands"
:key="brand.id"
:name="brand.name"
:price="brand.price"
></component>
But I dont think thats the correct way to do this....
try to change
import component from "src/components/component.vue";
to
import foo from "src/components/component.vue";
on your components section you just call foo instead of foo:component
I am not sure, but:
Looks like ${brand} is empty. Your function GetData() is async, so the <foo> is created before the GetData() has its data set/returned.
You can change
<foo v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To
<foo v-if="brands.length> 0" v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To make sure that the element is renderd after the data if set.
Note: v-if is when the html is rendered, v-show is just a css display hide, but the html is always renderd

How to redefine a Vee Validate error message for one Vue component?

I have a global validation rule, for example:
import { extend } from 'vee-validate';
import { required } from 'vee-validate/dist/rules';
extend('required', {
...required,
message: 'Please fill the field'
});
This rule is using for all Vue components across the project. But for one exact component I need to redefine the message Please fill the field to another one. Is it possible to change the message just for one Vue component?
You can specifiy specific messages for each ValidationProvider component using the custom-messages prop
<ValidationProvider rules="required" :custom-messages="{ required: 'required message' }">
<!-- ... -->
</ValidationProvider>
You can extract it on a data prop and use it for the providers in your component:
<template>
<ValidationProvider rules="required" :custom-messages="customMessages">
<!-- ... -->
</ValidationProvider>
<ValidationProvider rules="required" :custom-messages="customMessages">
<!-- ... -->
</ValidationProvider>
</template>
<script>
export default {
// ....
data: () => ({
customMessages: {
required: 'custom message'
}
}),
// ...
};
</script>

What is wrong with my vue-multiselect options?

I have a vue-multiselect component in a form, and the console reports that my options are undefined, even though I can see they are not. My options are fetched from a back end and put in the store well before this component is created.
The console error is
Invalid prop: type check failed for prop "options". Expected Array, got Undefined
Here is my component
<template>
<form action="#" #submit.prevent>
<section>
<div class="container">
<h2 class="subtitle">Details</h2>
<b-field label="Role" horizontal>
<multiselect
:options="roleOptions"
track-by="id"
label="title"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
></multiselect>
</b-field>
</div>
</section>
</form>
</template>
<script>
import Multiselect from "vue-multiselect";
import { mapState, mapActions } from "vuex";
export default {
name: "ProcessDetailsComponent",
components: {
multiselect: Multiselect
},
computed: {
roleOptions() {
return this.$store.state.processes.formData.process_roles;
}
},
};
</script>
<style scoped>
</style>
In the developer tools Vue inspector, I can see that the options look correct (to me). I've tried passing them in as props, computed values, mapped state - same problem every time.
If I swap the options out for a static array, defined in the data() function, it works ok. Can anyone confirm that I am implementing this correctly?
:options="roleOptions" expects an array. Make sure the vue variable is an array and not an object or undefined etc as mentioned above.
data(){
return{
roleOptions:[],
}
}

How to prevent the data/method sharing of looped components in Vue.js

I have the vue component with $emit into component and let it return the data from the component. I will use the component to update current page's data. the codes below
Template:
<Testing
#update="update">
</Testing>
<AnotherComponent
:data="text"
>
</AnotherComponent>
Script:
method(){
update: function(data){
this.text = data.text
}
}
it work perfectly if only this one.
Now , i need to make a button to add one more component.
I use the for loop to perform this.
Template
<div v-for="index in this.list">
<Testing
:name="index"
#update="update">
</Testing>
<AnotherComponent
:data="text"
>
</AnotherComponent>
</div>
Script:
method(){
addList : function(){
this.list +=1;
},
deleteList : function(){
this.list -=1;
},
update: function(data){
this.text = data.text
}
}
The add and delete function run perfectly.
However , they share the "update" method and the "text" data.
so , If I change the second component , the first component will also changed.
I think this is not the good idea to copy the component.
Here are my requirements.
This component is the part of the form, so they should have different name for submit the form.
The another component" will use the data from the "testing component" to do something. the "testing" and "another component" should be grouped and the will not change any data of another group.
Any one can give me the suggestion how to improve these code? Thanks
What happends is that both are using the data form the parent, and updating that same data.
It seems that you are making some kind of custom inputs. In that case in your child component you can use 'value' prop, and 'input' event, and in the parent user v-model to keep track of that especific data data.
Child component BaseInput.vue:
<template>
<div>
<input type="text" :value="value" #keyup="inputChanged">
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
}
},
methods: {
inputChanged (e) {
this.$emit('input', e.target.value)
}
}
}
</script>
And this is the code on the parent:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<base-input v-model="firstInputData"></base-input>
<p>{{ firstInputData }}</p>
<hr>
<base-input v-model="secondInputData"></base-input>
<p>{{ secondInputData }}</p>
</div>
</div>
</div>
<script>
import BaseInput from './BaseInput.vue'
export default {
components: {BaseInput},
data() {
return{
firstInputData: 'You can even prepopulate your custom inputs',
secondInputData: ''
}
}
}
</script>
In the parent you could really store the diferent models in an object as properties, and pass that object to that "The another component" , pass them as individual props... pass an array ....