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
Related
I've got a list of components where I'd like them all to be editable / replicate state to the parent component.
The list component exists as:
Vue.component("shortcuts", {
props: {
shortcuts: Array
},
template: '...<shortcut-entry v-for="(shortcut, index) in shortcuts" v-bind:key="index" v-bind="shortcut" #remove="remove(index)"></shortcut-entry>...'
})
It's used with a model like this:
<shortcuts v-bind:shortcuts.sync="shortcuts"></shortcuts>
Now each shortcut-entry component will contain lots of values which I would like to be propagated back to the top level list of objects:
Vue.component("shortcut-entry", {
props: {
mod_ctrl: Boolean,
mod_alt: Boolean,
mod_shift: Boolean,
keypress: String,
action: String,
...
},
Each of those properties exists as a separate checkbox / input on the page with (for example) <input ... v-model="action">. The way I understand it, I could wire the update events back to the parent component and do replacements there... but that sounds like a lot of boilerplate code.
Can I somehow propagate any modifications for those props back to the parent component automatically? (avoiding the "Avoid mutating a prop directly" warning)
It seems to work as I expect if I move every prop I currently have into another level (so I have props: {options: Object}, v-bind it with .sync and assign everything there), but I'm looking into some more explicit solution which actually declares the relevant options ahead of time.
You can use sync modifier with the props object notation together. Instead of v-bind="shortcut" use v-bind.sync="shortcut"
This way shortcut component can declare all props (instead of just options object) and still be able to notify parent about mutation
But you need to change how you bind your inputs. Instead of <input ... v-model="action"> you need to <input ... v-bind:value="action" v-on:input="$emit('update:action', $event.target.value)">. Problem with this is that different <input> types use different names for value and change event
To work around it and keep using v-model you can declare computed for each prop and use v-model="actionComputed"
computed: {
actionComputed: {
get: function() { return this.action },
set: function(value) { this.$emit('update:action', value) }
}
}
So the result is again lot of boilerplate...
TL:DR
If you want all props declared ahead of time (instead of single prop of type object), some boilerplate is necessary. It can be reduced by generating computed props for v-model on the fly (same way Vuex helpers generate computed to access and update Vuex state) but I don't think it is worth the effort. Just pass an object and let the <input> components mutate it's properties...
I want a child component to reload everytime the object, which i transfer to the child as a prop, changes. I read that VueJs can not detect a change in an Object. So far so good, I came up with the following Idea:
Everytime my Object changes, I perform a change in a normal variable which I also transfer via a prop to the child. My idea was it to "force" a rerendering of the child component through the change of the normal variable. But it seems not to work and I don't understand why it doesn't work.
Parent File:
<template>
<compare-value :ocean="ocean" :update="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
<script>
import CompareValue from '#/views/compare/CompareValue'
...
components: {
CompareValue
},
...
updateComponent: 0,
...
methods: {
reloadCompnent() {
this.updateComponent += 1;
},
getData() {
this.ocean.push({
userid: userId,
data1: this.result.data_john,
data2: this.result.data_mike,
data3: this.result.data_meika,
data4: this.result.data_slivina,
})
this.reloadCompnent() //Force the reload of the component
}
}
</script>
Child File:
<template>
{{ update }}
</template>
<script>
...
props: [
'ocean',
'update'
],
...
</script>
As far as I understood, a change of a normal variable triggers the component to be reloaded but it seems I oversee something.
Setting an existing Object prop is actually reactive, and so is adding a new object to an array prop. In your example, getData() would cause compare-value to re-render without having to call reloadComponent() (demo).
I read that VueJs can not detect a change in an Object.
You're probably referring to Vue 2's change-detection caveats for objects, which calls out addition or deletion of properties on the object.
Caveat example:
export default {
data: () => ({
myObj: {
foo: 1
}
}),
mounted() {
this.myObj.foo = 2 // reactive
delete this.myObj.foo // deletion NOT reactive
this.myObj.bar = 2 // addition NOT reactive
}
}
But Vue provides a workaround, using Vue.set() (also vm.$set()) to add a new property or Vue.delete() (also vm.$delete()) to delete a property:
export default {
//...
mounted() {
this.$delete(this.myObj, 'foo') // reactive
this.$set(this.myObj, 'bar', 2) // reactive
}
}
thanks for the answers and I tested your suggested answer and I would have worked but I did something else. I just replaced the :update with :key and it worked. After this action the Component is automatically reloaded.
The solution looks exactly like the one i posted in the question just one (importatn) tiny little thing is different. See below.
<template>
<compare-value :ocean="ocean" :key="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
Thanks and hope it will help others too.
Br
If I'm passing (a reference to) an Object as a prop is it OK to mutate values in the prop?
I'm developing a web app which will require a lot of values to be passed to a component, and I'm trying to find the best way of passing the values to the component and back to the parent.
From everything I've read mutating a prop is the wrong way to do things, because next time the component is updated the values are passed back to the child component overwriting the mutations. But only the reference to the object is passed so any mutations to the values in the object prop happen to the original object in the parent component. Also Vuejs does not complain about mutating props when this happens.
const subComponent = {
name: "subComponent",
template: `
<div>
Sub Component Input
<input type="text" v-model="objectProp.val1"></input>
</div>`,
props: {
objectProp: {
required: false,
default: () => {return {val1: "carrot"}}
}
}
}
const aComponent = {
name: "aComponent",
template: `
<div>
val1: {{mainObject.val1}}
val2: {{mainObject.val2}}
<sub-component :objectProp="mainObject"></sub-component>
</div>`,
data: function() {
return{
mainObject: {
val1: "foo",
val2: "bar"
}
}
},
components: {
subComponent
}
}
new Vue({
el: "#app",
components: {
aComponent
}
})
Here is a JSFiddle showing an object prop being mutated.
JSFiddle
Is mutating a prop bad practice?
Yes absolutely. In more complex applications it is very easy to lose track of where/what/why is mutated
What is the right way to handle state across different components?
In small projects you can really do whatever you want, because you will most likely be able to follow the logic - even after not looking at the project for a year. The possibilities include:
Mutating Props (ugly but will work)
Using Events to mutate state in parent components; take a look at the EventBus (better)
Use global shared state; look at Vuex (best, but a little more boilerplate)
In big projects, however, you should absolutely use Vuex. It is a module for Vue that adds global shared state to your app, which you can access and mutate from all places in your app.
I think I understand what you are trying to do, You want to pass data to a child component, mutate it and then give that data back to the parent component.
You never want to mutate the props data given to the child component, however you CAN mutate a local state of the data, which could be an exact clone of the prop.
You can do this in many ways, I normally use a computed property as suggested in the Vue documentation:
https://v2.vuejs.org/v2/guide/components-props.html
in the computed return, just return the data coming in from the property.
I'm trying to use the <component> element to dynamically display a chosen component. Each of these displayed components can take one of any number of data objects. Something like:
<div id="containers">
<component v-bind:is="currentView"></component>
</div>
var myVue = new Vue({
el:"#containers",
data:{
currentView: "my-component-one",
currentData: {...}
},
method: {
changeView: function(){
//change this.currentView
//change this.currentData
}
}
});
However, the Vue documentation says the v-bind:is attribute can be used to pass either a component name or the options object.
It is unclear how I would conditionally get an object of values for that component to use and also conditionally change which component is shown.
I am very green with using Vue (coming fresh off a knockout kick) so perhaps I am simply misunderstanding the intention of the component tag.
you can simply use v-bind
html
<component v-bind:is="currentView" v-bind="data"></component>
script
data()
{
return {
data: {
currentData: "example"
}
}
}
and it will pass currentData down to child. You can also add other properties along with it, including is.
If you need to change the component along with props, then you just change the data property, or whatever you want to call it.
https://codesandbox.io/s/7w81o10mlx
This example might help you understand it. https://jsfiddle.net/jacobgoh101/mLbrj5gd/
For passing Component Name
If the component is global, you can pass the component name to v-bind:is
for e.g.,
Vue.component('test1', {
template: `<div>test1</div>`
})
HTML
<component is="test1"></component>
For passing option
A Vue component is literally just a Javascript object with specific properties
for e.g.,
<component v-bind:is="{
template: `<div>test2</div>`
}"></component>
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>