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

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

Related

How to display data from Vuex store in life time in Vue

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>

Component works only once using router-link - Laravel vue

I am working on a project that uses Laravel Vue SPA, and I got a problem accessing the data of the single product, it's only working when I click once, but when I select again with other product, I can't get the product data, but the URL is correct it's changes the ID but can't access the data.
It is working when I click another link like Services then select product, it can display the product data.
Here is my HTML
<ul >
<li v-for="product in products">
<router-link :to="{ name: 'edit-product', params: { id: product.id } }">
{{ product.title }}
</router-link>
</li>
</ul>
My Vue Routes
const EditProduct = require('./components/EditProduct').default;
{
path: '/edit/product/:id',
component: EditProduct,
name: 'edit-product'
}
my EditProduct component
<template>
<div>
<h1>this.$route.params.id</h1>
</div>
</template>
Cheers
Look at this answer. You have to watch the productId and execute your logic again.
Possible solution
try to define a computed property inside your EditProduct component for get product id
computed: {
productId(){
return this.$route.params.id
}
}
and use productId inside your <h1> tag
<template>
<div>
<h1>{{ productId }}</h1>
</div>
</template>
update
solution :
add this watcher to your EditProduct component for reacting with params change
watch: {
$route(to) {
console.log(to.params.id)
// you can call your method here and pass product id to them for example: this.getProductDetails(to.params.id)
},
beforeRouteEnter (to, from, next) {
next(vm => {
//also call your method here => vm.getProductDetails(to.params.id)
});
},
you can read more about params changes here: Reacting to Params Changes

Vue - v-for with modifying values

Template:
<template>
<nav>
<router-link :to="key" v-for="(value, key) in state.menu" :key="key">{{value}} | </router-link>
</nav>
</template>
Code:
export default {
setup() {
const menu = ['home', 'news', 'about'];
const state = reactive({
menu: {}
});
menu.map(item => {
state.menu[item] = Common.locs[item];
});
return {
Common,
state,
}
}
}
I want to update
:to="key"
to run it through some filter or function to modify it to add the prefix something like "/other/", so instead of rendered
<a href="/home" ...>
I would have
<a href="/other/home" ... >
(Of course, const menu values are mocked for the sake of example; those are fetched from the service so I have no saying in that, and I don't want to modify them after fetching for other reasons)
So in general, my question is not regarding proper routing, but how do you modify v-for data (in my case: key) in the runtime if you need to?
(I guess I could modify mapping line into
state.menu["/other/" + item] = Common.locs[item];
but I want to preserve state.menu as I wrote. I want change to happen during the v-for rendering.)
Have you tried the template literal:
<template>
<nav>
<router-link :to="`/other/${key}`" v-for="(value, key) in state.menu" :key="key">{{value}} | </router-link>
</nav>
</template>

Push not updating array in DOM Vue

I am using Vue and am trying to make live search. But on updating the content of search, it doesn't get updated.
Data do get update in array, when checked in dev tools. But DOM don't get updated.
template
<div class="dropdown">
<input type="text" v-model="input" placeholder="Search" #keyup="searching" data-toggle="dropdown">
<span class="caret"></span>
<ul class="dropdown-menu">
<li v-for="(data,index) in availSearchData" :key="index">
{{data.name}}
</li>
</ul>
</div>
method
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
res.data.forEach(doc => {
this.availSearchData.push(doc);
});
});
}
}
I don't know where I am doing wrong.
Please help out if possible.
To add an item to the back of an array and get it to be reactive in Vue, below is what worked for me:
this.$set(this.items,
this.items.length,
JSON.parse(JSON.stringify(this.item))
);
The this.$set is Vue's inbuilt array manipulation function that guarantees reactivity.
The this.items is the array, this.items.length (NOTE: it is items.length NOT items.length - 1) is to push a new index to the back of the array and finally, JSON.parse(JSON.stringify(this.item)) is to clone the this.item into a new object before pushing into the array. The cloning part may not be applicable to you and I used this in variables because all the variables are declared in my data() function.
Use a computed property in your component and use that for parsing the template like this
<li v-for="(data,index) in availSearch" :key="index">
{{data.name}}
</li>
and computed property will be then
availSearch() {
return this.availSearchData;
},
so this computed property always return the array if it is updated.
Also if your response is the array that you want to use exactly, try this
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
Vue.set(this, 'availSearchData', res.data);
});
}
}
Possible explanations for this might be:
You don't declare the property in the component and thus normal
reactivity doesn't work.
You are using index as the key in your array. This might confuse the
reactivity system, so it does not necessarily know if the item
changed. Try using the name of the item as the key instead.
Try calling your function from mounted hook. I think the problem is that you are trying to show data when the DOM is not rendered yet. By calling your function in mounted you get data back after DOM has been rendered.
mounted() {
this.searching();
}
from Vue website "mounted: Called after the instance has been mounted, where el is replaced by the newly created vm.$el. If the root instance is mounted to an in-document element, vm.$el will also be in-document when mounted is called."

defining the layout of child component from parent in Vue js

I'm new to Vue and using Vue 2.2.1. I am wondering if it's possible to create a reusable component that can have its layout defined by its parent. For example, consider the following pseudo code:
// Parent template
<template>
<ul>
<li v-for="item in items">
<item-component :id="item.id">
<h1><item-title /></h1>
<p>
<item-description />
</p>
</item-component>
</li>
</ul>
</template>
// Child definition
<script>
export default {
data() {
return {
title: '',
description: ''
}
}
create() {
// do some async fetch
fetch(this.id)
.then((result) {
this.$data.title = result.title
this.$data.description = result.description
})
}
}
</script>
So, the use case is that the child component is responsible for the fetching of the data by id, but the parent is responsible for laying out the data. This way, I can keep the fetch logic in one place, but reformat the data however I want in various places.
Not sure if this is possible or not. I suppose I can extract the child's fetching functionality out into a mixin, but then I'd have to create a new component for each layout variation. What is the recommended way to handle this in Vue?
In general, when you want the parent to include content in the child, the means to do it is via a slot. Inside, a typical slot, however, the scope is the parent's scope, meaning it does not have access to data inside the child.
In your case, you would want to use a scoped slot, which is where the child is able to pass some information back to the parent to use.
// Parent template
<template>
<ul>
<li v-for="item in items">
<item-component :id="item.id">
<template scope="props">
<h1>{{props.title}}</h1>
<p>
{{props.description}}
</p>
</template>
</item-component>
</li>
</ul>
</template>
// Child definition
<script>
export default {
template:"<div><slot :title='title' :description='description'></slot></div>",
data() {
return {
title: '',
description: ''
}
}
create() {
// do some async fetch
fetch(this.id)
.then((result) {
this.$data.title = result.title
this.$data.description = result.description
})
}
}
</script>