Why Vue treats unspecified boolean props as false? - javascript

Please see this example
If I have a component like this:
<template>
<div v-if="on">
Hello
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
on: Boolean
},
mounted() {
console.log(this.on)
}
};
</script>
And then I render that component and specify nothing on the prop
<template>
<div >
<HelloWorld/>
</div>
</template>
This will output false instead of undefined?
Why is that?
Is there anyway I can detect if people didn't specify the prop, so I can use lodash _.isBoolean to detect it?
Currently, the only way I know is manually set the default to undefined.

You could set a default value on it and check if the value is still the same then the user didn't pass a prop, but there are instance where you can't really compare your value to its default, like if the prop is a function.
You could have this.$options.propsData inside of your component. If the prop is present here, the user has explicitly set it; default values aren't shown in.

Related

Vue - Access child component default props after mounted

I'm trying to figure out what the default properties (props) were for a child component. In this example, I have two components A and B. B wraps around A and A is passed properties. I'd like to figure out what the default values were for the component A from the component B which wraps around it, most importantly the types specified for the defaults. To be clear, in the mounted function of B I'd like to be able to see the default values and types of A assuming B will always have exactly 1 child component.
I've tried getting the child component using this.$children[0]._props in the mounted lifecycle hook, but those properties are the ones set. I've tried getting the properties earlier in the Vue lifecycle (like created, beforeCreate, beforeMount etc.) except they don't seem to exist until mounting. I've also inspected the this.$children[0] object using the browser console, and haven't found any default values for the props (only getter functions which retrieve the override defaults). I'm willing to pass extra data to B in order to get the default properties, but would prefer not to (since it seems redundant, i.e. I should know what the component "origin" was by looking at the this.$children[0] object).
Minimal example is located here: https://codepen.io/maxschommer/pen/wvaqGjx
I've included the HTML and JS below for quick reference.
JS:
Vue.component('A', {
name: "A",
props: {
prop1: {
type: String,
default: "The First Default Value"
},
prop2: {
type: String,
default: 'The Second Default Value'
}
},
template: `<div class="A">
<h1>A Prop 1: {{ prop1 }} </h1>
<h1>A Prop 2: {{ prop2 }} </h1>
</div>`
});
Vue.component('B', {
name: "B",
mounted: function() {
this.$children[0];
// I'd like to find some way to get the default properties
// of the child of this component (A) here (or in computed, etc.). Instead
// this gives the values which were set.
alert(JSON.stringify(this.$children[0]._props));
},
template:
`<div><slot></slot></div>`});
var parent = new Vue({
el: "#app",
template:
`<div class=templateField>
<B>
<A prop1="Overriding" prop2="Defaults"></A>
</B>
</div>`
});
HTML:
<div id="app">
</div>
PS: I'm a bit confused about the difference between components and elements when refering to Vue (I believe components are JS objects and elements are when they are rendered to html) so please correct my terminology if I'm getting it wrong.
You can access the original options object (the object you give Vue to construct component instances) from this.$options, so
mounted() {
const propDfns = this.$options.__proto__.props
const propTypes = Object.values(propDfns).map(p => p.type.name)
console.log(propTypes)
},
Components are not only JS objects. they are mixture of js, html or template and css

v-bind.sync does not pass object as props

I'm passing an object as a required prop to a component through v-bind.sync but I'm getting an error Missing required prop: "filters"
What am I missing here? It doesn't seem to be working as specified in the documentation:
v-bind.sync="doc":
This passes each property in the doc object (e.g. title) as an individual prop, then adds v-on update listeners for each one.
Here's my component which is called using <ListFilters v-bind.sync="filters" />
<template>
<div>{{ filters }}</div>
</template>
<script>
export default {
name: 'ListFilters',
props: {
filters: {
type: Object,
required: true
}
}
}
</script>
Adding a default for filters results in the component using the default instead of the parent value.
As stated in the highlighted passage you provided, any property in filters will be passed to the child component as a property of its own that way. So, it would need to contain another filters property (filters.filters) for it to be accessible in the child component under that name. To pass the parent's property itself, you would use v-bind:filters.sync="filters" instead.

Can a v-on directive directly modify a data() property of the parent?

I have an application which has Vue components as children.
The components pass back data to the parent via a this.$emit (numberchnaged below), which is caught at the parent level by a v-on (or #) directive, which in turns triggers a method.
This method then updates a data() property of the parent:
<template>
(...)
<Users #numberchanged="doNumCh"></Users>
(...)
</template>
<script>
(...)
export default {
components: {
Users
},
data() {
return {
u: "hello"
}
},
methods: {
doNumCh(value) {
this.u = value
}
}
}
</script>
This solution works but is quite verbose for just updating this.u with what <Users> sent back.
Is there a way to make the update right in the <Users> tag, something like
<Users #numberchanged="u=theValueReturedByUsers"></Users>
My problem is that I do not know how to extract theValueReturedByUsers, I only get hold of it in the method as value (in my example above).
Functionally, you're looking to have v-model behavior on your component. Vue provides for that. So you can say
<template>
(...)
<Users v-model="u"></Users>
(...)
</template>
which is a tidy view, as long as your Users component (side note: you should always have a hyphen in custom component names) takes the value parameter and $emits the input event.
See also v-bind.sync to work with props other than value.
The payload is reachable via $event.
For the code above, the solution would therefore be
<Users #numberchanged="u=$event"></Users>
You can do it like this (without write a method in parent) with using the variable $event which contains the value returned (Object or literal variable) from child component:
<users #numberchanged="{u=$event}"></users>

Inconsistent behaviour of props for Number/String in Vue.js 2.5.16

This is a long term existing issue for me, let's say I have a parent and child component.
// parent
<div>
// passing a dynamic value
<child :param="timestamp"/>
</div>
// Child
props: {
param: {
type: Number,
required: true,
}
},
When the param value passed into child component, it should pass the validation.
however, it shows the error
type check failed for prop "param". Expected Number, got String.
If I changed the type into String, it still showed the error but in a opposite way
type check failed for prop "param". Expected String, got Number.
Would be grateful to know how to solve this issue, thanks.
==========================================================================
Sorry for not explaining very well in first example.
So in my code base, I pass a variable to child component, the type of the value is always Number, let's say it's a timestamp, so when I pass the value, the inconsistent error appears all the time, which really confuses me.
Meanwhile, I use v-bind since I pass dynamic variable to child component.
I had the same issue and simply solved it by passing both possibilties in the prop definition, so instead of
props: {
myprop: Number,
[..]
}
I say it may be both:
props: {
myprop: [String, Number],
[..]
}
I realize this might not be a clean solution if the prop value has to be exactly of a certain type, but I think I just leave this here anyways.
Just have:
<child :param="12345"/>
You do not need to bind that way.
See this example:
https://codesandbox.io/s/ryv49jm594
App.vue
<template>
<div id="app">
<HelloWorld :param="12345" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
}
};
</script>
HelloWorld.vue
<template>
<p>{{ param }}</p>
</template>
<script>
export default {
name: "HelloWorld",
props: {
param: {
type: Number,
required: true
}
}
};
</script>

can you pass component props to mapGetters vuex

I'm starting to make a universal input vue component. Before I worry about changing the data in the input, I just wanted to get the initial value from the store.
I have something like this:
<template lang="html">
<input class="input-text" type="text" name="" :value="value">
</template>
<script>
import store from '#/store/index';
export default {
name: 'InputText',
props: [
'dataModel',
'propertyName',
],
computed: {
value() {
return store.state[this.dataModel][this.propertyName];
},
},
};
</script>
This works. Instead of the value function in computed. I wanted to leverage mapGetters. So I tried something like this instead:
...mapGetters({
value: `${this.dataModel}/${this.propertyName}`,
}),
As soon as I tried the latter I got undefined for both values this.dataModel and this.propertyName. The context of this changes when invoking mapGetters because were in a new object. Is there a way to pass component props into mapGetters? Is there a way to set the context of this to be the component rather than the object argument? Or was my original approach/another approach the correct way to tackle this problem?
First, don t import the store in components, its available as this.$store, so remove:
import store from '#/store/index';
You can't use this like you are in mapGetters because it doesn't exist in the context youre trying to use it yet. The component is still early in it's lifecycle when when setting up properties. As far as I know, the name must be defined beforehand.

Categories