VueJS - Cannot read property 'status' of undefined - vue.js

Although the output is correct. I have this error: Cannot read property 'status' of undefined. I suspect it is because listing is added after ajax call. How do I improve this?
<template v-for="status in showStatus">
<div class="col-sm-1">{{status}}</div>
</template>
<script>
var app = new Vue({
el: "#app",
data: {
listing: []
},
created: function(){
axios.get('/getListing')
.then(function (response) {
app.listing = response.data;
})
.catch(function (error) {
console.log(error);
});
},
computed: {
showStatus(){
return this.listing[0].status;
}
}
});
</script>

As you've guessed, this is because listing starts as an empty array which is populated at a later time.
Your computed property doesn't do any checks for this and this.listing[0] will be undefined early on.
You just need to add a check to your computed poperty
computed: {
showStatus () {
return this.listing[0]?.status || []
}
}
See Optional chaining (?.) if you haven't seen that syntax before.
An alternative would be to initialise listing with some safe default data
data: () => ({
listing: [{ status: [] }]
})

Related

Vue.js : data is visible on console.log but not in DOM

I using google places-api to get a single or list of places. for each place in the list of places I would like to get an additional data (such as website) - this is done from another api (places details).
The problem is that I can see all the data in google console but not in the DOM - only the data from the the first API is visible ( {{item.website}} property is empty)
here is my code:
<input type="text" class="form-control" id="searchPlace" v-on:keyup.enter="getPlaces()" placeholder="Type a name, address etc..." v-model="placeToSearch">
<div v-for="(item, key) in objPlaces" :key="key">
{{ item.name }} | {{item.place_id}} | {{item.rating}} | {{item.website}}
</div>
<script>
var myObject = new Vue({
el: "#app",
data: {
placeToSearch: "",
objPlaces: null,
},
methods: {
getPlaces() {
let places = null;
axios
.get('#Url.Action("GetPlace")', { params: { name: this.placeToSearch }})
.then((res) => {
places = res.data.results;
})
.finally(() => {
// iterate each place to get its website
places.forEach(function (el) {
axios
.get('#Url.Action("GetPlaceDetails")',{params: { placeId: el.place_id }})
.then((res) => {
el["website"] = res.data.result.website;
});
this.objPlaces = places;
console.log(this.objPlaces); // all the data is displayed
});
});
},
},
please note I am using server side to get the details from google api
You may find it easier to use async/await rather than the callback functions.
const myObject = new Vue({
el: "#app",
data() {
return {
placeToSearch: "",
objPlaces: null,
}
},
methods: {
async getPlaces() {
const res = await axios.get('#Url.Action("GetPlace")', {
params: { name: this.placeToSearch },
});
const places = res.data.results;
this.objPlaces = places.map(async (el) => {
const res = await axios.get('#Url.Action("GetPlaceDetails")', {
params: { placeId: el.place_id },
});
el.website = res.data.results.website;
return el;
});
},
},
});
Note 1: I haven't tested this, but the general idea is there.
Note 2: Missing try/catch to handle errors from the API.
You're missing the return statement inside data function. The data option should always be a function in the context of components which returns a fresh object.
data(){
return {
placeToSearch: "",
objPlaces: null,
}
}
You can read more about it from the documentation: https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function

What triggers a recalculation of a computed property in an async context?

I fetch some data from a JSON file to be processed. This asynchronous operation (the fetch) seems to disturb the functionality of computed() properties.
The code below works as expected -- data are not retrieved but hardcoded in mounted(). When you run the code and look at the console, you see a first computation for filteredTags (empty), then allTags are modified, which triggers a calculation of filteredTags.
→ everything is as expected
new Vue({
el: "#app",
data: {
posts: [],
tags: {},
allTags: {},
},
computed: {
// provides an Array of tags which are selected
filteredTags: function() {
let t = Object.keys(this.allTags).filter(x => this.allTags[x])
console.log("computed filteredtags: " + JSON.stringify(t))
return t
}
},
watch: {
},
mounted() {
const r = '{"tag1":["/posts/premier/"],"post":["/posts/premier/","/posts/second/"]}'
this.tags = JSON.parse(r)
// bootstrap a tags reference where all the tags are selected
Object.keys(this.tags).forEach(t => {
this.$set(this.allTags, t, true)
// this.allTags[t] = true
});
console.log("allTags in mounted: " + JSON.stringify(this.allTags))
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="tag in filteredTags">{{tag}}</div>
</div>
Now the same code, but with the case where {"tag1":["/posts/premier/"],"post":["/posts/premier/","/posts/second/"]} is retrieved via a HTTP call:
<div id="app">
<div :click="allTags[tag]=!allTags[tag]" v-for="tag in filteredTags">{{tag}}</div>
</div>
<script>
new Vue({
el: "#app",
data: {
posts: [],
tags: {},
allTags: {},
},
computed: {
// provides an Array of tags which are selected
filteredTags: function () {
let t = Object.keys(this.allTags).filter(x => this.allTags[x])
console.log("computed filteredtags: " + JSON.stringify(t))
return t
}
},
watch: {
},
mounted() {
// get source tags
fetch("tags.json")
.then(r => r.json())
.then(r => {
this.tags = r
// bootstrap a tags reference where all the tags are selected
Object.keys(this.tags).forEach(t => {
this.$set(this.allTags, t, true)
// this.allTags[t] = true
});
console.log("allTags in mounted: "+ JSON.stringify(this.allTags))
})
}
})
</script>
The output of the console is:
computed filteredtags: []
allTags in mounted: {"tag1":true,"post":true}
computed filteredtags: ["tag1","post"]
computed filteredtags: []
The first three lines are OK. Why is there an extra recomputation of filteredtags at the end? And more importantly -- why is the result empty?

variable declaration conflict while redirection to another route using router.push

I am creating a common httpService file from which my all http calls will be called. and in this on a http status 403 i want to redirect to '/profile' route. And after redirection it is showing error
[Vue warn]: The computed property "errors" is already defined in data.
found in
---> at resources/js/components/Admin/Profile.vue
It looks like it is a conflict of data declaration.
httpservice.js
getRequest: function (parameters,callBackFunction) {
let response = '';
let url = 'url' in parameters ? parameters["url"] : "";
let data = 'data' in parameters ? parameters['data'] : "";
axios
.get(url,{
params:data
}).then(data => {
if(data.status == 200){
response = data.data.data;
callBackFunction(response);
}
}).catch(error => {
if(error.response.status == 403){
console.log(router);
router.push({path: '/profile'});
}
});
}
profile.vue
data() {
return {
errors: [],
form: new Form({
password: "",
new_password: "",
confirmed_password: ""
}
No, it's not that complicated.
Computed properties depends on the values defined in data properties. And these properties will be extended/assigned into the Vue instance directly (so that you can call this.errors).
Therefore, you can't reuse the same property name in both data and computed object. Because reusing the name means overriding the existing data, which means computed values can't depend on data values anymore.
In your code, you only shows:
data() {
return {
errors: [],
form: new Form({
password: "",
new_password: "",
confirmed_password: ""
}
According to the error:
[Vue warn]: The computed property "errors" is already defined in data.
I believe you also wrote something like:
computed: {
...
errors: function(){}
...
}
in Profile.vue too. Just change one of those names will solve your problem.
EDIT
To demonstrate the problem:
new Vue({
el: '#app',
data(){
return {
errors: [
'error 1',
'error 2'
]
}
},
computed: {
errorString(){
return `Errors: ${this.errors.join(', ')}.`
}
},
template: `<div id="app">{{errorString}}</div>`
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
The above works fine, while the below will cause an unexpected result and a same Vue warning that you got:
new Vue({
el: '#app',
data(){
return {
errors: [
'error 1',
'error 2'
]
}
},
computed: {
errors(){
return `Errors: ${this.errors.join(', ')}.`
}
},
template: `<div id="app">{{errors}}</div>`
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

VueJS does not update DOM after fetch data from API?

I am trying to create an example about the list of photos and I see a trouble when binding data to the component after call API.
JS code:
<script>
// photo item
Vue.component('photo-item', {
props: ['photo'],
template: `<li>{{ photo.name }}</li>`
});
// List of photos
Vue.component('photo-list', {
props: ['photos'],
template: `
<ul id="photo-list">
<photo-item v-for="photo in photos" :photo="photo"></photo-item>
</ul>`
});
new Vue({
el: "#photo_detail",
data: {
photos: []
},
created: function() {
axios
.get('/api/photos')
.then(function (response) {
this.photos = response.data; // Data existed
})
.catch(function (err) {
console.log(err);
});
}
})
</script>
HTML code
<main id="photo_detail">
<photo-list v-for="photo in photos" :photo="photo"></photo-list>
</main>
After fetching all photos from API and as my understand then the variable photos will auto binding and VueJS will update DOM.
VueJs 2.1.6
Any help.
Thanks!
Issue is with your this value inside function() which has this value scoped to axios instead of vue instance .
or you can use (response)=> to use this directly
new Vue({
el: "#photo_detail",
data: {
photos: []
},
created: function() {
var self=this;
axios
.get('/api/photos')
.then(function (response) {
self.photos = response.data; // Data existed
})
.catch(function (err) {
console.log(err);
});
}
})
Your code is not correct.
Problems:
It will be better to define used components for each component, like
components: {photoItem}.
In your axios callback you use function and that means, that you use wrong context inside (this.photos). Use arrow function
(() => {}) instead of the function () {}
The directive v-for requires directive :key=""
I've fixed it below.
// photo item
const photoItem = Vue.component('photo-item', {
props: ['photo'],
template: `<li>{{ photo.name }}</li>`
});
// List of photos
const photoList = Vue.component('photo-list', {
// define used components
components: {photoItem},
props: ['photos'],
template: `
<ul id="photo-list">
<!-- add :key="" directive -->
<photo-item v-for="(photo, index) in photos" :key="index" :photo="photo"></photo-item>
</ul>`
});
new Vue({
el: "#photo_detail",
// define used components
components: {photoList},
data: {
photos: []
},
created: function() {
// axios.get('/api/photos')
// use arrow function
setTimeout(() => {
this.photos = [{name: 'Photo 1'}, {name: 'Photo 2'}];
}, 1000);
}
})
<script src="https://unpkg.com/vue"></script>
<main id="photo_detail">
<photo-list :photos="photos"></photo-list>
</main>

Cannot access this whilst debouncing search request in Vue with Lodash?

I have a simple search input:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js" type="text/javascript"></script>
<div id="app">
<input type="text" v-model="search">
</div>
When you enter a value it will query a remote API to fetch and then display the data:
...
new Vue({
el: '#app',
data: {
people: []
search: ''
},
watch: {
search() {
// Rate limit
this.debouncedSearch()
}
},
methods: {
debouncedSearch: _.debounce(() => {
console.log(this)
// self = this
// io.socket.put('/search', this.search, (people, jwres) => {
// self.people = people
// })
}, 500)
},
created(){
this.people = locals.people
}
})
The problem here is that console.log(this) returns undefined.
I have used this in another application and it works so battling to understand why not here.
Is there something that I have done wrong there, seems to be correct but no matter what I try I cannot access the scope of the Vue application in that debouncedSearch method?
To solve the issue just replace the arrow function with function() {}
Replace this:
methods: {
debouncedSearch: _.debounce(() => {
console.log(this) // undefined
}, 500)
},
With This:
methods: {
debouncedSearch: _.debounce(function () {
console.log(this) // not undefined
}, 500)
},
Hope to help others took me a lot of time to figure it out.
Pretty sure your problem is the use of the this-preserving function style. You need a way to refer to the Vue object you're creating (it's not this at the point your debounce function is defined). One way would be to do
const vm = new Vue({
el: '#app',
...
methods: {
debouncedSearch: _.debounce(() => {
console.log(vm)
// io.socket.put('/search', vm.search, (people, jwres) => {
// vm.people = response
// })
}, 500)
},