Vue - Iterating through an object after deleting the child objects - vue.js

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>

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>

Prop passed to child component is undefined in created method

I am using Vue.js 2.
I have a problem with passing value to the child component as a prop. I am trying to pass card to card-component.
In card-component I can access the prop in the Card goes here {{card}} section.
However when I try to access it in created or mounted methods it's undefined.
Parent:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<card-component :card="place.card"></card-component>
</div>
</div>
</div>
</template>
<script>
import CostComponent from './CostComponent';
import CardComponent from './CardComponent';
export default {
components: {
CostComponent, CardComponent
},
props: ['id'],
data() {
return {
place: []
}
},
created() {
axios.get('/api/places/' + this.id)
.then(response => this.place = response.data);
}
}
</script>
Child:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<ul class="list-unstyled">
Card goes here {{card}}
</ul>
</div>
</div>
</div>
</template>
<script>
import CardItemComponent from './CardItemComponent';
export default {
components: {
CardItemComponent
},
props: ['card'],
created() {
console.log(this.card); // undefined
},
mounted() {
console.log(this.card); // undefined
},
}
</script>
I did a lot of googling but none of the solutions I found have fixed my issue.
This is purely a timing issue. Here's what happens...
Your parent component is created. At this time it has an empty array assigned to place (this is also a problem but I'll get to that later). An async request is started
Your parent component creates a CardComponent instance via its template
<card-component :card="place.card"></card-component>
at this stage, place is still an empty array, therefore place.card is undefined
3. The CardComponent created hook runs, logging undefined
4. The CardComponent is mounted and its mounted hook runs (same logging result as created)
5. Your parent component is mounted
6. At some point after this, the async request resolves and changes place from an empty array to an object, presumably with a card property.
7. The new card property is passed down into your CardComponent and it reactively updates the displayed {{ card }} value in its template.
If you want to catch when the card prop data changes, you can use the beforeUpdate hook
beforeUpdate () {
console.log(this.card)
}
Demo
Vue.component('CardComponent', {
template: '<pre>card = {{ card }}</pre>',
props: ['card'],
created () {
console.log('created:', this.card)
},
mounted () {
console.log('mounted:', this.card)
},
beforeUpdate () {
console.log('beforeUpdate:', this.card)
}
})
new Vue({
el: '#app',
data: {
place: {}
},
created () {
setTimeout(() => {
this.place = { card: 'Ace of Spades' }
}, 2000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<card-component :card="place.card" />
</div>
See https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
If place is meant to be an object, you should not be initialising it as an array. Also, if your CardComponent relies on data being present, you may want to conditionally render it.
For example
data () {
return { place: null }
}
and
<card-component v-if="place" :card="place.card"></card-component>
then CardComponent will only be created and mounted after place has data.
Make sure you have props: true in the router file. It is a simple solution but many of us forget this.
{
path: '/path-to',
name: 'Name To',
component: Component,
props: true
}

Why does computed change data when assigning data to a variable?

I just realized a strange behavior of Vue.js when using computed properties. Maybe I am missing something and this is the right behavior but for me it doesn’t make sense. If you have a look at the following code you will see inside the computed property I created a new variable and assigned an array defined in “data”. I then pushed some new data into the newly created variable. Now the array in “data” has also changed! Why is that?
new Vue({
el: "#app",
data: {
items: ['foo', 'bar']
},
computed: {
someComputed() {
let some = this.items
some.push('foobar')
return some
}
}
})
<div id="app">
{{ someComputed }} – {{ items }}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
This is because "Call by Reference", you are just referencing the array from data. It's like a pointer, some and this.items are pointing to the same object.
If you want a copy of this.items you need to call.
let some = this.items.slice()
This way you are getting a whole new object and not just a new "reference".
Normaly JS is "Call by Value" but for objects and arrays, the value is the reference.
Edit:
Have a look at:
Javascript passing arrays to functions by value, leaving original array unaltered
You should make a copy of this with this.items.slice(0)
new Vue({
el: "#app",
data: {
items: ['foo', 'bar']
},
computed: {
someComputed() {
let some = this.items.slice(0)
some.push('foobar')
return some
}
}
})
<div id="app">
{{ someComputed }} – {{ items }}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Vue passing data to child component

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>