Push not updating array in DOM Vue - vue.js

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."

Related

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>

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

Use moment().fromNow() with a vue component in array

How can I use Momentjs to format a date that is inside my vue component? The moment().fromNow() function works fine when I manually put in a date, but I want to use the date that is fetched from my API.
This is currently what it looks like, I put the part where I need help with in *.
Vue.js
<div class="post d-flex flex-row" v-for="(post, i) in Post" :key="i">
<h6 class="card-subtitle mb-2 text-muted">Posted **{{moment(datePosted).fromNow()}}** by {{post.user}}</h6>
</div>
data() {
return{
Post: []
}
},
async created() {
try{
const res = await axios.get(url)
this.Post = res.data;
} catch(err){
console.log(err)
}
}
Variable datePosted seems undefined. The format function should be something like {{ moment(post.datePosted).fromNow() }}. Also don't use variables which differ just by the case: post and Post.
In my opinion, you could map res.data inside created and append attribute with formatted data to the object.
E.g:
this.Post = res.data.map(post => ({
...post,
datePost: moment(post.date).fromNow()
})
Obviously, I cannot know if post.date is proper attribute. You have to type there the proper one. Also make sure you imported moment js module.
Then in the template, you could use:
{{ post.datePost }}

Method in vue js runs infinitely

I have a method that is called in vue js template. However, when I run the web site, this method is called infinitely
<template>
<div class="strip_list wow fadeIn" v-for="(row, index) in model.data">
<i class="icon_star voted" v-for="(val, index) in getOverallRating(row.id)">
</i>
</div>
</template>
<script>
methods: {
getOverallRating(id)
{
axios.get(`${this.apiReview}?id=${id}`).then(response =>
{
this.overall = response.data
})
return this.overall
}
}
</script>
I expect to give an id of the user, then the method should get an ID send it to the laravel controller, get calculate the rating according the entries in DB and return the result.
So, what you want to do is remove anything that would generate an api call out of your templates loop. What happens is, that every time the data changes, you re-render the template, and since you have an api call in your template, every time you render you request new data, that's why you're getting an infinite loop.
You should store the data you get from the api in a variable, and initiate API calls from outside of the loop.
<template>
<div class="strip_list wow fadeIn" v-for="(row, index) in model.data">
<i class="icon_star voted" v-for="(val, index) in overall(row.id)">
{{val}}
</i>
</div>
</template>
data: () => {
overall: {}
},
methods: {
getAll() {
// loop through all rows and make api request
this.model.data.forEach(r => this.getOverallRating(r.id))
}
getOverallRating(id) {
// make api request
axios
.get(`${this.apiReview}?id=${id}`)
.then(response => {
// and save into overall, where the id is the key
this.overall[id] = response.data
})
},
},
created(){
// initialize loading during create
this.getAll();
}
This can be further improved by not rendering anything 'till all rows are fetched. You could do that by defining another variable in data, that gets populated during getAll, and updated every time the api gets a response. But Ideally you'd be able to call the API for all reviews at once, not one at a time.

Vuejs v-model binding with a select box inside a slot

Good afternoon,
I seem to have occured an issue with v-model binding in scoped slots.
I've tried to create a universal API form that would allow me to hook any URL to it, add any amount and any type of DOM elements inside a scoped slot and use data fetched from API accordingly.
So far I've managed to do the first part - fetch data and pass it to elements inside; the issue I'm having now is thanks to one-way data flow - selecting an option inside of <select></select> doesn't seem to update selectId accordingly - and I get it, that's the pattern it follows... now, how do I work around it?
edit:
If I copy this select directly into the component (without passing via scoped slot) and replce props.* with just * (props.selectedId to selectedId) - it'll work flawlessly.
It only doesn't work because props are one-way.
<template>
<form :action="action" :method="method">
<slot :selectedId="selectedId" :results="results"></slot>
</form>
</template>
<script>
export default
{
props: ['action', 'method', 'url'],
data: () =>
({
results: [],
selectedId: 0,
}),
created()
{
setTimeout(() =>
{
axios.get(this.url).then(response => (this.results = response.data))
}, 500)
},
}
</script>
and HTML:
<api-form action="/blog" method="POST" url="/api/v1/blog">
<template slot-scope="props">
<select class="form-control mb-3" v-model="props.selectedId">
<option v-for="entry, i in props.results" :value="entry">#{{ entry.title }}</option>
</select>
<button class="btn btn-danger">Delete</button>
</template>
</api-form>
Gosh, I keep posting here and finding an answer afterwards.
4 hours of googling - nothing, then I post here and suddenly come up with a solution.
For anyone having the same issue, this is caused by the fact that all non-components, if you apply $emit to it, it'll be called from <Root>, meaning you have to edit your:
created()
{
this.$on('update:selectedId', (value) =>
{
this.selectedId = value
})
},
And change it to this:
created()
{
this.$root.$on('update:selectedId', (value) =>
{
this.selectedId = value
})
},
You can pass a callback as a slot prop that will modify the data being passed also as a slot prop, here's an answer of mine on a similar question v-model and scoped slots not working?