Props interdependance in VueJS - vue.js

I want to add some linking dependencies between my props in my VueJS component.
For example in my component on props declaration, i would like to stipulate that if a prop is present, then another one should be required, but not required at all if the previous props is not there.
props: {
url: {
type: String,
required: true,
},
isShared: {
type: Boolean,
default: false,
},
isSharedByOtherMember: {
type: Boolean,
default: false,
},
archivedId: {
type: String,
required: isSharedByOtherMember ? true : false, // This is not working, bit is there a way to do so ?
},
After reading vuejs docs :
Note that props are validated before a component instance is created, so instance properties (e.g. data, computed, etc) will not be available inside default or validator functions.
Is there a way to still do this in props declaration for better readability/understandability after ?
Thanks in advance

You could use the validator property for prop.
Vue docs has this example: (https://v2.vuejs.org/v2/guide/components-props.html#Prop-Validation)
// Custom validator function
propF: {
validator: function (value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
You could define a validator method in Vue methods section.
Something like this:
export default {
props: {
isSharedByOtherMember: {
type: Boolean,
default: false
},
archivedId: {
type: String,
default: null,
required: false,
validator: this.validateArchiveId(),
errorMessage: 'Archived ID required when isSharedByOtherMember has value of true.'
}
},
methods: {
validateArchiveId () {
return this.isSharedByOtherMember
}
}
}

Related

How can I return an empty instance from a getter in Vuex?

I have a store that by default returns undefined for a particular attribute:
// store.js
export const state = {
locationLoading: false,
locationsLoading: false,
locations: [],
location: undefined, <--
};
In my component, I am using a getter within the computed attribute:
// component.vue
...
computed: {
location() {
return this.$store.getters['location/location']; // returns undefined or a location
},
},
...
Within my data for a location I have an array:
// location.json
...
"name": "my location",
"messaging": [
{
"email": {
"fromName": "No Reply <noreply#example.com>",
"fromAddress": "noreply#example.com"
}
}
],
If I am visiting a url such as /locations/123 everything works great. There are no errors; the page renders correctly including validation.
If visit /locations for example, get a list of all locations.
The issue I am having is using vuelidate for my validation. Because in my /locations route, there isn't a location to load, my getter returns undefined (as expected).
The error I am getting is
Cannot read properties of undefined (reading 'email')"
Which makes sense since my location is undefined.
Here is what I am using to validate messaging:
// component.vue
<input type="text" v-model.trim=" $v.location.messaging.$each.$iter[0].email.fromAddress.$model"
/>
I could pass a location in as a prop that contained every attribute. For example:
// component.vue
props: {
location: {
type: Object,
required:false,
default:() => ({
name: '',
messaging: [{
email: {
fromName: '',
fromAddress: '',
},
}],
...
Doing this clears all errors, but I feel that is pretty fragile. If I decide to add a new attribute to a location, I need to remember to add it here too.
How can I return an empty object that satisfies validation?
You can try with optional chaining :
<input type="text"
v-model.trim="
$v.location?.messaging?.$each.$iter[0].email.fromAddress.$model"
/>

Why does vue emit propagate to parent?

I'm new to Vue. I have a child component like below. I added a validator via the 'emits' property and I assumed that if the validator fails then the parent event handler doesn't get called but it does. I also don't see a way to validate the input in the parent or in the child. I know I could add a method in the child component that I assign to the emit property and check it in the child and then only call $emit' if it returns true but it seems backwards. I'm sure I'm missing the point here, someone please clarify because it seems to me that the 'emits' property validator is only for debugging purposes and doesn't actually modify the app behavior, just throws a console warning. To me that is a bug waiting to happen. Is there some vue config setting that I need to change to enable the behavior I was expecting? What am I missing? (btw, when I run npm show vue version I get 3.2.45 if that would matter.
The console showing that the parent handler was called.
Child Component:
<script>
export default {
data() {
return {
username: "",
age: null,
};
},
emits: {
"new-user": function (obj) {
return obj && typeof obj.username == 'string' && typeof obj.age == 'number';
},
},
methods: {
newUser() {
const output = { username: this.username, age: this.age };
this.$emit("new-user", output);
},
},
};
</script>
The parent component:
<template>
<h2>Hello</h2>
<user-data #new-user="newUser"></user-data>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
newUser(val) {
console.log('emitted newUser',val)
},
},
};
</script>
Passing a function to emits will not affect whether the event is emitted, it is only meant for validation. See docs here
though I see that it's not very clear from the docs:
export default {
emits: {
submit(payload) {
// return `true` or `false` to indicate
// validation pass / fail
}
}
}
To make the emit conditional you could use
export default {
data() {
return {
username: "",
age: null,
};
},
emits: ["new-user"],
methods: {
newUser() {
if (typeof this.username == 'string' && typeof this.age == 'number'){
const output = { username: this.username, age: this.age };
this.$emit("new-user", output);
}
},
},
};
You're spot on.
Emit validation only shows a console warning. It is not a mechanism to not emit something.
Similar to prop validation, its primary use is for development. Very useful for large teams or if you're making a component library.

How to compute a property based on an object with fallback

I have a component that receives an object as prop, like this:
props: ['propObject']
Then, there's a default object defined (I use VueX, so it's actually defined as a $store getter, but to make it simpler, let's say it's defined in the data method) in the data:
data() {
return {
dataObject: {defaultValueA: 1, defaultValueB: 2}
}
}
And I'd like to have a computed property that would behavior like this:
computed: {
computedObject() {
return Object.values(this.propObject).length > 0 ? this.propObject : this.dataObject;
}
}
However, I know this is not possible because Vue watchers don't watch for changes in the key/value pairs of an object.
I have tried to go with a watched property, like this:
props: ['propObject'],
data() {
return {
object: {},
defaultObject: {}
}
},
watch: {
propObject: {
handler: function() {
this.setComputedObject();
},
deep: true
}
},
methods: {
setComputedObject() {
this.object = Object.values(this.propObject).length > 0 ? this.propObject : this.defaultObject;
}
},
mounted() {
this.setComputedObject();
}
However, the watcher handler is not being called at all when the propObject changes, but if I call it directly via console, it works. Is there any way that I can make the computedObject become reactive?
you need to use Vue.set/vm.$set where you change the props (in source component)
for example
changeProp(){
this.$set(propObject,'newprop','newval');
}
and then just you regualr compouted in the target component (the component which receive the prop)
source : https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats

Access this / vm instance from props default (VueJS)

I have a plugin that sets some variables to vue's object prototype.
I need to access these variables from a prop's default property. How
can I achieve this?
Using the following example, webpack throws some undefined error.
//...
props: {
size: {
type: String,
required: false,
default: this.$myPlugin.size
}
}
For Vue 2, you can specify the default as a function that returns the default value. That should have access to the current instance as this.
props: {
size: {
type: String,
required: false,
default () {
return this.$myPlugin.size
}
}
}
The relevant line in the Vue source code is here if you're curious. Note that the function is explicitly called with vm as its this value.

Vue prop validation is not called

I create a component with input properties:
export default {
data() {
:
:
},
props: {
rowsContent: {
type: Object,
default: null,
validator: function(value) {
console.log("In validator");
}
},
rowsPerPage: {
type: Number,
default: 10,
},
}
I tried to pass different type of parameters, and got no error message.
Moreover, no "In validator" message is printed to console.
Any idea?
I don't know the reason, but it is working if I use component tag like <tag></tag>. If I use like <tag/>, it does not work. See example here. https://codesandbox.io/s/z6rlzl998p
EDIT: Vue does not support self-closing tags as components: https://github.com/vuejs/vue/issues/8664 (as mentioned in comment)