Vue passing data to child component - vue.js

I have an issue where I'm trying to pass an object 'through' one component, which is the overall layout, to child components which sit inside it. I've made a simplified example where I basically have a <ul></ul> template and an <li></li> template.
I seem to be losing the reference for each one by the time they are created. When I create them, I get the error:
vue.js:1023 [Vue warn]: Error when evaluating expression "model.id":
TypeError: Cannot read property 'id' of undefined (found in component:
<demo-list-item>)
What am I doing wrong?
I think I just have a fundamental missing from my knowledge of Vue... I'm really, really new to it and am learning from their website – so this could be a really obvious / silly mistake.
HTML:
<div id="app">
<demo-list></demo-list>
<script id="demo-list-template" type="text/x-template">
<ul>
<demo-list-item v-for="item in items"></demo-list-item>
</ul>
</script>
<script id="demo-list-item-template" type="text/x-template">
<li data-id="{{model.id}}">{{ model.name }}</li>
</script>
</div>
JavaScript:
// define
var DemoList = Vue.extend({
template: '#demo-list-template',
data : function(){
return {
'items' : [
{
'id' : 1,
'name' : 'this'
},
{
'id' : 2,
'name' : 'that'
},
{
'id' : 3,
'name' : 'something'
},
{
'id' : 4,
'name' : 'nothing'
}
]
}
}
});
// List Item
var DemoListItem = Vue.extend({
template : '#demo-list-item-template'
});
// register
Vue.component('demo-list', DemoList);
Vue.component('demo-list-item', DemoListItem);
// create a root instance
var Vue = new Vue({
el: '#app',
});
Demo:
http://codepen.io/EightArmsHQ/pen/vXYWgz

According to the Component props doc, you can pass data to child component like this :
<child name="value"></child>
and
<child :name="value"></child>
for dynamic props
So, in your template, when you loop over items array, you got item object. Just pass it to your child component
<demo-list-item v-for="item in items" :item="item">
Also, in your child component, you have to tell that you attempt to get a prop named item
var DemoListItem = Vue.extend({
template : '#demo-list-item-template',
props: ['item']
});
You can validate props, set default value, etc (see doc)
Now, in your child template, you have access to item property
<li data-id="{{item.id}}">{{ item.name }}</li>

Related

Vue 3: access VueComponent object placed in slots

I'm working on tab component and I want to render tab labels in parent component by getting child's slot, named 'label'
In Vue 2.x I could approach that, by referring to $slots property of tab component, in Tabs.vue:
<template>
<section class="tabs">
<ul class="tabs-labels">
<li
v-for="tab in tabs"
:key="tab._uid"
:class="[{'active': tab.isActive}, 'tab-label']"
#click="selectTab(tab);"
>
{{ tab.$slots.label }}
</li>
</ul>
<div class="tabs-content">
<slot/>
</div>
</section>
</template>
<script>
export default {
name: 'Tabs',
data () {
return {
tabs: [],
};
},
mounted () {
// filter tabs in case there were additional vue components placed in slots
this.tabs = this.$children.filter(tab => tab.$options.name === 'TabContent');
},
methods: {
selectTab (selectedTab) {
// set isActive property of the tab by comparing their uids
this.tabs.forEach(tab => {
tab.isActive = (tab._uid === selectedTab._uid);
});
},
},
};
</script>
TabsContent.vue:
<template>
<div v-show="isActive" class="single-tab-content">
<slot/>
</div>
</template>
<script>
export default {
name: 'TabContent',
data () {
return {
isActive: false
};
},
};
</script>
Here, when the tab label clicked, in Tabs.vue I iterate through tabs array and setting their isActive property, comparing their uid and uid of selectedTab
But in Vue 3.x API of slots has changed, so I changed the way of getting tab contents:
from
this.tabs = this.$children.filter(tab => tab.$options.name === 'TabContent');
to
this.tabs = this.$slots.default().filter(tab => tab.type.name === 'TabContent');
but as I understand, it getting only vNodes, not actual VueComponent that rendered, so when I'm executing selectTab method
tab.isActive = (tab._uid === selectedTab._uid);
it updates only isActive properties for tabs, that were saved in tabs array, not for actual tab contents, so v-show never changes.
Is there any way to get actual rendered VueComponents from <slots>? Or maybe this approach is wrong from the beginning and I should try something else?
Edit
CodeSandboxes for both versions:
Vue 2.x -ignore the error about refering to children during render, it's a bug on CodeSandbox
Vue 3.x
Its a bit more complicated with Vue 3. You will want to look into using provide and inject. here is a good example.
https://gist.github.com/cathrinevaage/4eed410b31826ce390153d6834909436
sandbox - https://codesandbox.io/s/happy-rubin-z414h?file=/src/App.vue
The example above is using typescript however you get the idea.

looping through variable from rest api and ading it to a v-list

I am having some difficulties with the vue.js. The main problem is that I am getting this error :
Property or method `response` is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for `class-based` components, by initializing the property.
My main idea is to loop through the response (which is just an array) and add it to my v-list to have it in shape of something like this :
Instead of having create, read etc. to have my elements of array, and I am wondering how to even start with this problem.
like this is the part with my list in vue.js, I know that I think I need to use v-for method but I cant even start it without solving the error.
<v-list-group>
<v-list-item #click="getHosts()">
{{response}}
</v-list-item>
<v-list-item-group>
</v-list-item-group>
</v-list-group>
</v-list>
and this is the function that gets the array.
getHosts(){
axios.get('http://127.0.0.1:8000/something')
.then((response)=>{
console.log(response.data)
return response
})
}
I've added this function in export default in section methods, I've read about other sections and thought maybe beforeMount but I still got an error.
Thanks for any clues/help/solutions!
Instead of returning the response directly. You can bind the response in the data property.
Working Demo (For demo purpose I am using v-for instead of v-list) :
var vm = new Vue({
el: '#vue-instance',
data() {
return {
hostList: []
}
},
methods: {
getHosts() {
axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
this.hostList = response.data;
}).catch((error) => {
console.warn('API error');
});
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="vue-instance">
<button v-on:click="getHosts">Get Hosts!</button>
<ul>
<li v-for="host in hostList">
{{ host.name }}
</li>
</ul>
</div>

Pass data from blade to vue and keep parent-child in sync?

I know that in Vue parents should update the children through props and children should update their parents through events.
Assume this is my parent component .vue file:
<template>
<div>
<my-child-component :category="category"></my-child-component>
</div>
</template>
<script>
export default {
data: {
return {
category: 'Test'
}
}
}
</script>
When I update the category data in this component, it will also update the category props in my-child-component.
Now, when I want to use Vue in Laravel, I usually use an inline template and pass the value from the blade directly to my components (as for example also suggested at https://stackoverflow.com/a/49299066/2311074).
So the above example my my-parent-component.blade.php could look like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component inline-template>
<my-child-component :category="{{ $category }}"></my-child-component>
</my-parent-component>
But now my-parent-component is not aware about the data of category. Basically only the child knows the category and there is no communication between parent and child about it.
How can I pass the data from blade without breaking the parent and child communication?
I just had to pass the category to the inline-template component through props like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component :initcategory="{$category}}" inline-template>
<my-child-component v-model="category"></my-child-component>
</my-parent-component>
In my-parent-component I had to set the props and initialize is using the create method:
export default {
props: {
initcategory: '',
},
data() {
return {
category: '',
};
},
created(){
this.category = this.initcategory;
}
}
Now my my-parent-component is fully aware of the category and it can communicate to the child using props and $emit as usual.
Your reference to this answer is different altogether from what you are looking for!
He's binding the :userId prop of the example component but not the parent component or in simple words: Any template using the example vue can either pass a string prop or bind :userId prop to a string variable. Following is similar:
<example :userId="{{ Auth::user()->id }}"></example>
OR
<example :userId="'some test string'"></example>
So you should rather assign {{ $category }} to a data variable but rather binds to a child component prop which will have no effect on the parent.
In the following snippet you're only binding the string but rather a data key:
<my-child-component :category="{{ $category }}"></my-child-component>
Update
See the following example which will change the h1 title after 3 seconds
// HelloWorld.vue
<template>
<app-name :name="appName" #appNameChanged="appName = $event"></app-name>
</template>
<script>
export default {
props: ['name'],
data() {
return {
appName: null
}
},
mounted() {
// NOTE: since Strings are immutable and thus will assign the value while objects and arrays are copied by reference
// the following is just for the purpose of understanding how binding works
this.appName = this.name;
}
}
</script>
The template which renders the app title or you can say the child component
// AppName.vue
<template>
<h1>{{ name }}</h1>
</template>
<script>
export default {
props: ['name'],
mounted() {
setTimeout(() => {
this.$emit('appNameChanged', 'Change App')
}, 3000);
}
}
</script>
And here's how it is being used in the welcome.blade.php
<div id="app">
<hello-world :name="'Laravel App'"></hello-world>
</div>

Vue - Iterating through an object after deleting the child objects

I am having an issue which is well documented on SO and other forums, where one iterates through an array and gets an error as the object might not be defined in the DOM or might not have loaded yet.
For example the below - if name or child is iterated too you will get an undefined error - as the child has not been defined. However if you were to add { child: { name: '' } } to the parent object it would work fine as it has been defined.
<p> {{ parent.child.name }} </p>
data: () => ({
parent: {}
})
One can get around that by testing to see if the parent object had no data like so:
<div> v-if="Object.keys(parent).length != 0" >
<p> {{ parent.child.name }} </p>
</div>
The issue I am having is that if I:
1. Create the page with the nested objects.
2. Add new data to the object.
3. Delete the added data.
I get an undefined error as the nested array no longer exits.
I can re-add the empty nested array again, but their must be a more slick way to check if the object is empty.
In vue you can use watch property to keep the track.
new Vue({
el: '#app',
data: () => ({
parent: {
child: {}
}
}),
watch: {
parent: function(val) {
console.log(val.child.length);
if (val.child.length === 0) {
this.parent.child = {};
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p> {{ parent.child.name }} </p>
</div>

How can I access the value of a nested vue.js component?

I have a component called lbcontainer and a second component called lbitem.
Now I want to nest any number of lbitem components into one lbcontainer component.
The lbcontainer component has a method that should access all lbitem components that I have nested in the lbcontainer component.
Problem is: with ref I can get to the item via this.$ref.lbitem, but this only works in the component declaration, but not when I use the component later in HTML.
Vue.component('lbcontainer', {
methods: {
"showChildren": function() {
console.log(this.$refs);
}
},
template: `
<div>
<slot></slot>
<a #click='showChildren'>Show children</a>
</div>
`
});
Vue.component('lbitem', {
data: function() {
return {
value: ""
}
},
template: `
<input v-model="value"></span>
`
});
new Vue({
el: "#app",
data: {
},
methods: {
}
});
<div id="app">
<lbcontainer>
<lbitem ref="item"></lbitem>
<lbitem ref="item"></lbitem>
</lbcontainer>
</div>
When I press the button the console.log shows an empty object. How can I access the nested children?
Here is jsfiddle
Avoiding using $ref in vue ...
( $ref is populated after the first render...)
child can only communicate with their parent by event ...
or parent have all data and send to their child by props.
if parent and chidrend have to edit the data you can use v-model or .sync;
https://codesandbox.io/s/p95x1mxykm