How to display data from Vuex store in life time in Vue - vue.js

I want o show data from Vues store. So first I want to check if authentication true, if it is, I want to show data from Vuex. Here is my shortcode:
<li v-if="authenticated">
Hello, {{ getUser.attributes.first_name }}
</li>
computed: {
getUser() {
console.log(this.$store.state.user)
return this.$store.state.user;
}
},
But I am getting error like you see in the picture below, why do you think it might be? Why first the object is coming empty and then object is filled?

Add a condition to the v-if directive because at the first rendering the attributes property is not available :
<li v-if="authenticated && getUser.attributes">
Hello, {{ getUser.attributes.first_name }}
</li>

Related

VueJS making API calls for every item in v-for and returning them to the right position

Thank you in advance.
So I am fetching list of blog categories via API and rendering it in a list using v-for.
I also need to fetch the amount of blogs in every category and place them beside the category.
But the issue is I am calling a method that calls the api.
<li v-for="item in sidebar" :key="item.identifier">
<nuxt-link
tag="a"
:to="{
name: 'blog-page',
query: { category: item.identifier }
}"
>{{ $localize(item.translations).title }}
{{ getBlogCount(item.identifier) }}
</nuxt-link>
</li>
You know what it shows already example is Animals [Object Promise]
methods: {
async getBlogCount(identifier) {
axios
.get(
"https://example.com/posts?fields=created_at&filter[category.category_id.identifier]=" +
identifier +
"&meta=*"
)
.then(count => {
return count.data.meta.result_count;
});
}
}
What is the best way to handle this kinda thing?
You better call async methods in mounted or created hooks, and set the result to data, and then, use that data in template.
I'd suggest handling this in Script, instead of HTML Template.
What you can do is, depending on when the sidebar is initialized (maybe in the mounted hook), call getBlogCount method to fetch blog counts for each item in sidebar and store that may be in an array or object (or as a separate key-value pair to that same sidebar item object) and then use that data structure to display count values in the template.
Assuming the sidebar is populated in mounted hook and that it's an array of objects, you can do the following:
<template>
<li v-for="item in sidebar" :key="item.identifier">
<nuxt-link
tag="a"
:to="{
name: 'blog-page',
query: { category: item.identifier }
}"
>{{ $localize(item.translations).title }}
{{ item.blogCount }}
</nuxt-link>
</li>
</template>
<script>
mounted () {
// after the sidebar is populated
this.sidebar = this.sidebar.map(async item => {
item.blogCount = await this.getBlogCount(item.identifier)
return item
})
}
</script>
Hope this helps you out

Vue CLI clickable dynamic url from a news API

I'm fetching data from newsapi.org and I want there to be a clickable link after the headlines so that you can read more about the article. But I can't make it work.
Maybe this is too advanced for me since I'm no good at coding. I thought maybe there was a simple way of making the dynamic urls work.
<template>
<div class="api">
<h1>Latest gossip</h1>
<br />
<div v-for="item of items" v-bind:key="item.id">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
<a v-bind:href="adress">
{{ item.url }}
</a>
</div>
</div>
</template>
I use axios.
<script>
import axios from "axios";
export default {
name: "Api",
props: ["articles"],
data() {
return {
items: [],
adress: "item.url"
};
},
mounted() {
this.getInfo();
},
methods: {
getInfo() {
axios({
method: "GET",
url:
"https://cors-anywhere.herokuapp.com/https://newsapi.org/v2/top-headlines?country=se&category=entertainment&apiKey=XXX",
dataType: "json",
headers: {
"X-Requested-With": "XMLHttpRequest",
"Access-Control-Allow-Origin": "*"
}
}).then(res => {
/* eslint-disable no-console */
console.log(res.data);
this.items = res.data.articles;
});
}
}
};
</script>
You can use vue-router in that case. You create a route with a parameter /news/:id in router/index.js eg.
const router = new VueRouter({
routes: [
{ path: '/news/:id', component: SingleNews }
]
})
then instead of
<a href=""/></a>
use
<router-link :to="{ name: 'news', params: { id: news.id }">{{news.headline}}</router-link>
And finally, retrieve parameter in a SingleNews.vue component using
this.$route.params.id
You can read more about vue-router and dynamic routes in the documentation https://router.vuejs.org/
So from what I understand, you're going to have a separate url for each different news article. It seems like there is a slight misunderstanding about how that information will be passed into the anchor tag, where it should be coming from, and how you're using data.
First, let's address your data method. If you look at the structure of your data you can see that you have an items property, which is an array and an adress property, which is set to a string.
data() {
return {
items: [],
adress: "item.url"
};
},
The problem here is that adress is not dynamic. It will always be the literal string "item.url", which is to say that it will always represent those characters ("item.url") in that order, but it doesn't actually represent any specific item's url property.
The good news is that you should already be seeing the correct url displayed on your page here:
<a v-bind:href="adress">
{{ item.url }}
</a>
Remember that an anchor tag in this case has two parts:
1. What we're displaying to the user.
2. Where we're telling the browser to redirect to.
The proper syntax here (without Vue) would be something like:
<a href="https://somelinktogoto.com">
Some text to display
</a>
What you're currently saying is: "I want an anchor tag to display the item's url to the user and redirect to whatever is stored in the data property called adress". Since adress is only storing the string "item.url", if you inspect your html that's been generated in your browser, I would suspect that all of your anchor tags look something like this:
<a href="item.url">
https://somenewsarticle.com
</a>
Luckily, the fix here is simple. Since you're already using v-bind, the href can use dynamic information, or information that's stored in your data somewhere, which can be referenced by its name. Then you can choose to display anything you'd like to your user. You can also delete your adress property from data, because it's not serving any purpose if all the urls are contained within items.
Try:
<a v-bind:href="item.url" target="_blank">
Read more...
</a>
data() {
return {
items: [],
}
}
Also, no one is good at coding at first, just keep trying to understand how data is flowing and you'll get there!
This line worked out great! Thank you!
<a v-bind:href="item.url" target="_blank">Read more here: </a>
I also deleted the adress data.

VueJS: v-model on <span> element ? How to handle that?

I have this simple component displaying user info:
<div class="m-card-user__details">
<span class="m-card-user__name m--font-weight-500">
{{ firstname }} {{ lastname }}
</span>
<a class="m-card-user__email m--font-weight-300 m-link">
{{ loginEmail }}
</a>
</div>
Script
<script>
export default {
data () {
return {
loginEmail : this.$store.getters.user.loginEmail,
firstname: this.$store.getters.user.firstName,
lastname: this.$store.getters.user.lastName,
}
}
};
</script>
Problem is that if another component change the value of the firstname property in the VueX store, I see that the value is well updated on the store but not on my component here..
How can i set the 2 ways bindings on a element ?
Attaching a store variable directly to data() will break the 2-way-binding.
Use a computed property for that, like:
computed: {
loginEmail() {
return this.$store.getters.user.loginEmail;
}
}
and then use the computed on the span, like {{ loginEmail }}, as you would normally.
Improvement: If you want, you can return the entire ...getters.user (as a object) with
computed, like:
computed: {
user() {
return this.$store.getters.user;
}
}
and then use it on your span like {{ user.loginEmail }} and so on.
This will save you some lines, increase readability and possibly a tiny bit of performance.
You can also directly use $store in your template.
<div class="m-card-user__details">
<span class="m-card-user__name m--font-weight-500">
{{ $store.getters.user.firstName }} {{ $store.getters.user.lastName }}
</span>
<a class="m-card-user__email m--font-weight-300 m-link">
{{ $store.getters.user.loginEmail }}
</a>
</div>
I haven't tried if this works with getters, but I don't see a reason why it wouldn't. Now you could maybe argue it's an anti-pattern, but I'd prefer it over having a computed property solely for this purpose.
See also https://github.com/vuejs/vuex/issues/306.
Edit: Because you mention 2-way binding: You shouldn't do this to update the $store, use actions and mutations instead. In this case, it's fine as it's essentially a 1-way binding where the state flows to the innerHTML of your <span>s.

VueJS v-for unwanted behaviour

I get this problem whenever I modify an array that is used to render a v-for list.
Let's say I've got a v-for list of three items:
<ul>
<li v-for="item in items"></li>
<ul></ul>
<ul>
<li>One</li> <!-- Has focus or a specific child component -->
<li>Two</li>
<li>Three</li>
</ul>
Add a new item to the items array:
<ul>
<li>New Item</li> <!-- Focuses on this item, the child component seems to be moved here -->
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
The focus seems to move...
Please have a look at a fiddle that illustrates the problem https://jsfiddle.net/gu9wyctr/
I understand that there must be a good reason for this behaviour, but I need to manage it or avoid completely. Ideas?
EDIT:
I've just realized that my explanation is rather ambiguous. Here's an updated fiddle to illustrate the problem https://jsfiddle.net/keligijus/d1s4mjj7/
The problem is that the input text is moved to another element...
My real life example. I've got a forum-like list of posts. Each post has an input for a reply. If someone publishes a new post while other user is typing in a reply, the input that this user is typing in is moved to another post. Just like the example in the fiddle.
Providing key is the answer!
https://v2.vuejs.org/v2/guide/list.html#key
When Vue is updating a list of elements rendered with v-for, it by default uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will simply patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item. This special attribute is a rough equivalent to track-by in 1.x, but it works like an attribute, so you need to use v-bind to bind it to dynamic values (using shorthand here):
<li v-for="(item, index) in items" :key="'item-'+item">
<input :id="'item-'+index" type="text" style="width:80%;">
</li>
Updated fiddle to show that this works https://jsfiddle.net/keligijus/d1s4mjj7/3/
Try this:
var app = new Vue({
el: '#app',
data: {
messages: [
{ message: 'Hello Vue!', id: 0 },
{ message: 'Hello Vuex!', id: 1 },
{ message: 'Hello VueRouter!', id: 2 }
],
msg: null,
focus: 'item-1'
},
mounted () {
document.getElementById(this.focus).focus()
setTimeout(() => {
this.messages.unshift({ message: 'Focus moves!', id: 3 })
}, 2000)
setTimeout(() => {
this.messages.unshift({ message: 'Moves again...', id: 4 })
this.msg = `I suppose this happens because of the way DOM is updated and I understand there must a good reason for this. However I need to avoid this behaviour. How can I do this?`
}, 4000)
},
updated: function () {
document.getElementById(this.focus).focus()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="(message, index) in messages">
<input :id="'item-'+message.id" type="text" v-model="message.message" style="width:80%;">
</li>
<li v-if="msg">{{msg}}</li>
</ul>
</div>
Basically I make id the same even when new items are added, and then I can track the focused item, and focus them again even after updated.

What's the point of using track-by attribute along with v-for?

I'm a newbie at vuejs. It seems to me the track-by attribute is a little hard to understand. The following example shows the track-by attribute has something to do with the duplicate elements in an array.But how is the v-for attribute implemented under the hood? What's the behavior when there are duplicate elements in an array and how track-by="$index" comes to make a difference?
new Vue({
el: '#app',
data: function() {
return {
items: [
'User Connected',
'Message',
'Message',
'User Connected',
'Message'
]
}
},
methods: {
addItem: function(item) {
this.items.push(item);
}
}
})
<div id="app">
<button #click="addItem('User Connected')">Add Connected</button>
<button #click="addItem('Message')">Add Message</button>
<ul>
<li v-for="item in items" track-by="$index">{{ item }}</li>
</ul>
<pre>
{{items | json}}
</pre>
</div>
https://jsfiddle.net/uuw4z0kr/2/
In order to be reactive and fast, Vue re-uses DOM elements whenever possible. So if it has already rendered the DOM for a particular item, it will save it to use anytime that needs to be rendered again. If something is removed from the array, then added back in, it will be faster to use existing HTML.
But this causes issues when the array elements are not unique. Vue can't distinguish them. track-by tells Vue which aspect of each item is unique, so it can know when to re-use DOM elements. If your array is a series of objects with an id attribute, you can use track-by='id'. But if the objects don't have a unique field, track-by='$index' associates each object with its position in the array. This is inherently a unique attribute, so it suppresses the error for duplicate entries.