Vue: Access values over api (nested) - javascript

Hi Stackoverflow Community,
In my template I try to show data which I receiver over an api call. When I log the api get response this is what I receive:
Now I try to show the data in location with the following code:
{{ userdetails.location.location_name }}
My script looks like this:
data() {
return {
userdetails: [],
};
},
methods: {
getUserData() {
DataService.getUserById()
.then((response) => {
this.userdetails = response.data;
console.log(response);
})
...
created() {
this.getUserData();
},
The strange thing is, I can see the location_name in my application. But I also receive an error:
I think I made somewhere a mistake, but I can't find it. Do you have any ideas what I am doing wrong?
Thank you in advance for all your tipps!

At the start, when the template tries to access userdetails.location.location_name, it is an empty list, so you will see the error. Only once the DataService updated userdetails will the nested properties exist.
The easiest way to prevent this is to start out with userdetails as something which is 'falsey' and then use v-if to determine whether to render:
<div v-if="userdetails">{{userdetails.location.location_name}}</div>
data() {
return {
userdetails: undefined,
};
},
Note however that if your DataService could ever return a dict which doesn't have the location property, you will need to do more testing in v-if to determine if it can be rendered.

Related

Vuex Getter Undefined

I am new to Vue.js and experiencing an issue with Vuex modules and Axios. I have a "post" component that retrieves a slug from the router and fetches data with Axios which is then retrieved with Vuex Getters.
I am able to retrieve data successfully but then I still see this error on my DevTools, "TypeError: Cannot read property 'name' of undefined"
Due to this error I am not able to pass this.post.name to Vue-Meta.
Codes
Post.vue
computed: {
...mapGetters(["post"]),
},
mounted() {
const slug = this.$route.params.slug;
this.fetchPost({ slug: slug });
},
methods: {
...mapActions(["fetchPost"]),
/store/modules/post.js
const state = {
post: [],
};
const getters = {
post: (state) => {
return post;
}
};
const actions = {
async fetchPost({ commit }, arg) {
try {
await axios.get("/post/" + arg.slug).then((response) => {
commit("setPost", response.data);
});
} catch (error) {
console.log(error);
}
},
};
const mutations = {
setPost: (state, post) => (state.post = post),
};
export default {
state,
getters,
actions,
mutations,
};
Your getter is utterly wrong: a state getter is supposed to be a function that takes in the entire state as a param and retrieves whatever you're interested in from it. Your version...
const getters = {
post: (state) => {
return post;
}
};
...takes in the state as a param but doesn't use it. Instead, it returns a variable (post) which has not been defined in that context.
Which will always return undefined, regardless of current value of state.post.
And, as you already know, JavaScript can't access property 'name' of undefined.
To get the current value of state.post, use:
const getters = {
post: state => state.post
}
Or
const getters = {
post: (state) => { return state.post; }
}
... if you fancy brackets.
Also, out of principle, I suggest initializing your post with an empty object {} instead of an empty array [].
Changing variable types as few times as possible is a very good coding habit, providing huge benefits in the long run.
Edit (after [mcve])
You have a bigger problem: the import from your axios plugin returns undefined. So you can't call get on it. Because you wrapped that call into a try/catch block, you don't get to see the error but the endpoint is never called.
I don't know where you picked that plugin syntax from but it's clearly not exporting axios. Replacing the import with import axios from 'axios' works as expected.
Another advice would be to namespace your store module. That's going to become useful when you'll have more than one module and you'll want to specifically reference a particular mutation/action on a specific module. You'll need to slightly change mapActions and mapGetters at that point.
See it working here.

Add Vue.js computed property to data gathered from a server

Coming from Knockout.js, where you can simply create an observable everywhere by defining it, is there something similar in Vue.js?
let vm = {
someOtherVar: ko.observable(7),
entries: ko.observableArray()
};
function addServerDataToEntries(data) {
data.myComputed = ko.pureComputed(() => vm.someOtherVar() + data.bla);
vm.entries.push(data);
}
addServerDataToEntries({ bla: 1 });
In my Vue.js project, I'm getting a list of objects from the server. For each of those objects, I want to add a computed property that I can use in a v-if binding. How can I achieve that?
I'm not familiar with the way Knockout does it but it sounds like a Vue computed. Create a data object to hold your fetched data:
data() {
return {
items: null
}
}
Imagine fetching it in the created hook (or Vuex, wherever):
async created() {
const response = await axios.get(...);
this.items = response.data;
}
Create your computed:
computed: {
itemsFormatted() {
if (!this.items) return null;
return this.items.map(item => {
// Do whatever you want with the items
});
}
}
Here is a demo using this pattern where I'm loading some data and printing out a filtered result from it. Let me know if I misunderstood what you're looking for. (You can see the original fetched data in the console.)

Vue Component Props are not being updated

In my data I have an object program. I load a list of objects asynchronously and add it to program.sections.
async loadSections() {
if (this.user == null && this.program.sections) { return }
await this.$store.dispatch('loadProgramSections', {program: this.program, user: this.user});
console.log('loaded Sections', this.program);
// this.$set(this.program, 'sections', this.progrmm.sections)
// this.$nextTick(() => { this.$forceUpdate(); })
},
My UI looks like this:
<Section :program="program" ></Section>
So I pass my program object down to my Sections component
In my console log I can see that the field programm.sections is indeed my array of objects but my UI does not get changed. When I put my UI code from the Sections component directly in to my page, it gets updated and the data is dispalyed correctly but when I use the component the props are not being updated correctly.
I already tried the commented lines in my code but it doesn't work.
Any Ideas?
Assign a new object reference in this.program:
async loadSections() {
if (this.user == null && this.program.sections) { return }
await this.$store.dispatch('loadProgramSections', {program: this.program, user: this.user});
console.log('loaded Sections', this.program);
// assign a new object reference
this.program = Object.asssign({}, this.program);
},
Also make sure you initialize program in data() section.
The main issue is that Vue cannot detect the changes in some situations.
e.g. set a item to an array or set an additional property to an object
That's the crux of the problem. You might need Caveats.

vue computed property not able to get data

I am currently experiencing an issue where the computed() property is not able to get data. Although data was already initiated in created() property. Am I doing it wrong? Please advise how I can fix this issue.
const randomPlayers = {
template:
`
<input type="text" v-model="search_player">
<div v-for="player in modPlayers" v-if="list_of_random_players!=null">
<p>{{player.firstname}}</p>
<p>{{player.lastname}}</p>
<div>
`,
props: ['data'],
data (){
return{
list_of_random_players: null,
search_player: null
}
},
created(){
this.get_random_players()
},
computed: {
modPlayers(){
return this.list_of_random_players.filter( person => {
return !this.search_player ||
( person.firstname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1 || person.lastname.toLowerCase().indexOf(this.search_player.toLowerCase()) > -1)
})
}
},
methods: {
get_random_players: function(){
$.post(
url:'random_url'
data: {
players: data
}
).done((success)=>{
this.list_of_random_players: JSON.parse(success)
})fail((err)=>{
console.log(err)
})
}
}
}
I get the following two errors:
(1) TypeError: Cannot read property 'filter' of null
(2) TypeError: this.list_of_random_players.filter is not a function
From Vue: "When a Vue instance is created, it adds all the properties found in its data object to Vue’s reactivity system. When the values of those properties change, the view will “react”, updating to match the new values."
So data is a function that returns an object but as mentioned by #Sovalina you are not returning it so you cannot access its properties. You need to add return and change null to []:
data() {
return {
list_of_random_players: [],
search_player: []
}
},
or you can do without return and like a regular object:
data: {
list_of_random_players: [],
search_player: []
}
When your Vue component is used multiple times, it is better to use it like a function(first case).
"When defining a component, data must be declared as a function that returns the initial data object. Why? Because there will be many instances created using the same definition. If we still use a plain object for data, that same object will be shared by reference across all instance created! By providing a data function, every time a new instance is created we can call it to return a fresh copy of the initial data."
Reference:link
It might be just a typo but you need to add : to methods as well.

VueJS list rendering anomaly

I already read some similar topics about my problem, but still I can’t figure out, what can be the problem is.
Long story short, I have an array of objects, and these objects also have an array, called “messages”. The parent object has some additional data, and my problem is caused by the child array, the messages.
The problem is:
The messages array is filled up by the server. After it, I successfully render a list of it’s content, show it on the web page, so far so good. But as soon as a new data arrives from the server, the rendered list is not updating itself. I checked the array’s content via the Vue dev plugin, and the new message was there, but the rendered list still remained in it’s original state.
The twist in the story is, that I created a simple test array, which is very similar to the original one, and also rendered on the page. When a new message arrives, I push it to both of them, and to my surprise, each one is updating itself perfectly. For the sake of curiosity, I commented out the test array rendering, and believe it or not, the original array does not update itself anymore on the page. After I remove the commented test array render, both of them works again.
The vue component:
/**
* Root
*/
import Vue from 'vue'
import axios from 'axios'
import inputComponent from './chat/input.vue'
new Vue({
el: '#chat-root',
components: { inputComponent },
data() {
return {
userID: document.getElementById('user').value,
ready: false,
roomID: "",
roomInstances: [],
test: [
{
messages: []
}
],
pollTime: 5000
}
},
created() {
axios.get('chat/init')
.then(resp => {
this.roomID = resp.data.roomList[0]._id
this.roomInstances = resp.data.roomList
this.fetchMessagesFromServer()
this.poll()
this.heartBeat()
}).catch(err => console.log(err))
},
computed: {
getMessages() {
return this.roomInstance.messages
},
roomInstance() {
return this.roomInstances.filter(i => i.id = this.roomID)[0]
}
},
methods: {
setRoom(id) {
if (this.roomID === id)
return
this.roomID = id
},
fetchMessagesFromServer() {
axios.get(`chat/get/${this.roomID}`)
.then(resp => {
this.roomInstance.messages = resp.data.messages
this.ready = true
}).catch(err => console.log(err))
},
heartBeat() {
setInterval(() => axios.get(`heartbeat/${this.userID}`), this.pollTime - 1000)
},
poll() {
axios.create({ timeout: this.pollTime })
.get(`poll/${this.userID}`).then(resp => {
if (resp.data.data) {
this.roomInstance.messages.push(resp.data.data.message)
this.test[0].messages.push(resp.data.data.message)
}
this.poll()
}).catch(err => console.log(err))
}
}
})
Template:
extends base
block scripts
script(src="/dist/chat.bundle.js")
block content
#chat-root(style="margin-top: 50px" v-cloak)
// Hack! Supposed to be an axios request?
input(v-if="false" id="user" value=`${user._id}`)
input-component(:room="roomID")
p {{ roomID || "Whoo hooo hoooo!" }}
span(v-for="room in roomInstances" #click="setRoom(room._id)") {{ room.name }} |
div(v-if="ready")
div(v-for="message in getMessages")
p {{ message }}
hr
div(v-for="fe in test[0].messages")
p {{ fe }}
For one, you are not finding the roomInstance correctly in the function for that computed property. You should just use the find method and return i.id === this.roomID in the callback instead of i.id = this.roomID:
roomInstance() {
return this.roomInstances.find(i => i.id === this.roomID);
}
The other issue might be when you are setting roomInstance.messages in the axios.get().then callback. If roomInstance.messages is undefined at that point, you'll need to use the Vue.set method (aliased on the Vue instance via this.$set) in order to make that property reactive:
this.$set(this.roomInstance, 'messages', resp.data.messages)
Here's the Vue documentation on Change Detection Caveats.

Categories

Resources