v-model on input change is heavy on performance - javascript

So I have a page rendering a v-list based on an array like so :
<v-list-tile v-for="item in array">
{{item}}
</v-list-tile>
and a dialog with a v-text-field :
<v-dialog>
<v-text-field v-model="myInput">
</v-text-field>
</v-dialog>
For now it's pretty normal.
But with a performance test, I saw that for every event triggered by a change on myInput model (like a key press) the v-for is also triggered re-rendering the list when they are actually not related.
On my huge array, it's a serious problem and make the UI really laggy. I think it's a normal behavior for a vuejs application, but I was wondering if I could precisely tell wish element to check for re-rendering.
I tried some v-if statements but it didn't do the trick.
I hope that there is an answer to that, i guess i'm missing something.
If you want to test what i'm talking about here is a ready to go html file, please debug it with your debug console, you will see a [vue warn] message of the duplicated key attesting of the fact that the v-for is indeed called for every key press.
Imagine now if the array (here items) is way bigger than that, and wrapped into complex components, making that call is just too heavy on performance when we are just aiming to change the "myInput" value.
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{data}}
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
<input v-model="data"></input>
</div>
</body>
<script>
new Vue({
el: '#app',
data: () => ({
data: '',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
</script>
</html>

Here's a codepen showing the inner loop in its own component
Codepen.io
I've added Date.now() after items[x].message list items to show when the list is being rerendered.
In case codepen ever goes down:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
Main vue: {{data}}
<loop-component :data="loopdata"></loop-component>
<input v-model="data"></input>
<input v-model="loopdata"></input>
</div>
<script>
Vue.component('loop-component', {
props: ['data'],
data() {
return {
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
},
template: `
<div>
Loop component: {{ data }}
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.message + ' Date.now(): ' + Date.now() }}
</li>
</ul>
</div>
`
});
let app = new Vue({
el: '#app',
data: () => ({
data: '',
'loopdata': '',
items: [
{message: 'Foo'},
{message: 'Bar'},
]
}),
});
</script>
</body>
</html>

Try using .lazy modifier to sync after change events.
<input v-model.lazy="data"></input>
https://v2.vuejs.org/v2/guide/forms.html#lazy
EDIT
#IVO GELOV is right, when a component changes, this re-render. The solution is split your component into several child components.
https://v2.vuejs.org/v2/guide/reactivity.html
This is a code using slots to make it look like your example.
HTML
<div id="app">
<new-component>
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
</new-component>
</div>
Javascript
Vue.component('new-component', {
data: () => {
return {
data: ''
}
},
template: `
<div>
<div>{{ data }}</div>
<slot></slot>
<input v-model="data"></input>
</div>`
})
new Vue({
el: '#app',
data: () => ({
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})

Since Vue 2.0+ whenever a change is detected - the whole component is re-rendered. If you want to avoid that - split your component into several child components.
Your example does not prove your point - the fact that there is a warning about duplicate keys inside the v-for does not mean that v-for is re-evaluated on each keypress. To confirm my statement - just change your code like this:
<li v-for="(item,idx) in items" :key="idx">
Now there is no warning.

Related

vue3 render template syntax (component) in loop

I have the following vue3 code:
<template>
{{click}}
<ol>
<li v-for="item in items" :key="item" v-html="item"></li>
</ol>
</template>
<script setup>
const click = ref();
const items = [
'<p>text</p><a #click="click+1">click</a>',
'<p>text2</p><p><router-link :to="{name: \'home\'}">home</router-link></p>'
];
</script>
How can I get the #click and router-link component to be compiled/rendered correctly in the output?
Vue does not render the string content to html. Does anyone have an idea how I can get this to work? I don't wont to hard code the li-items.
Your code is a big red flag. Thats not the way how to work with vue.js
Here an example how it could be done:
<template>
<ol>
<li v-for="item in items" :key="item.link" >
<router-link :to="item.link">{{ item.name }}</router-link>
</li>
</ol>
</template>
<script setup>
const click = ref();
const items = ref([
{
name: "Link1",
link: "/home"
},
{
name: "Link2",
link: "/login"
}
]);
</script>
I have no idea what this code is supposed to do #click="click+1"
Also, the v-html attribute isnt a attribute that is common used. Because its also dangerous and leads to XSS vulnerabilities if you are not carefully.

Vue (2.6.14) - v-for not displaying data with an api import (axios)

The idea
I'm trying to build a display where activities are shown with some filters. The data comes from an API generated by the CMS. The filters are not shown in the code since its not relevant.
Problem
When manually defining the 'items' in the data property the v-for list rendering displays fine and gives the desired output. When pulling the data from the api and assigning them to items the v-for is not displaying anything.
My Thoughts
I think that the v-for is run before the api request is finished putting the data into the 'items' value. Currently I'm using 'created' property to fire the function, also used 'Mounted()' before, this also didn't work.
Versions Vue 2.6.14, Axios 0.21.1
Vue Code
var vue = new Vue({
el: '#app',
data: {
items: null,
},
created: function () {
this.fetchData()
},
methods: {
fetchData: function() {
axios
.get('/api/activities.json')
.then(response => (this.items = response.data.data))
}
}
})
Templating
<div id="app">
<ul class="example">
<li v-for="item in items">
{{ item }}
</li>
</ul>
</div>
Your code seems to be working just fine. Look below, I just replaced your api call with a dummy REST api call and it's working just fine. Please console out the data from response.data.data and see if you are really receiving an array there.
Vue.config.productionTip = false
Vue.config.devtools = false
let vue = new Vue({
el: '#app',
data: {
items: null,
},
created() {
this.fetchData()
},
methods: {
fetchData: function() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(response => { this.items = response.data })}
}
})
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="app">
<ul class="example">
<li v-for="item in items">
{{ item.id }} - {{ item.name }}
</li>
</ul>
</div>
</div>

Vuejs warning: infinite update loop in a component render function

working through my first project in vue js.
Here, looping through different tabs and show correct content for each tab when clicked on.
https://codepen.io/anon/pen/vWPMGq?editors=1010
problem here is with line
this.cardData = $(".card-content").html(this.coinInfo[this.activeTabName]);
but I'm not sure how to fix this.
You shouldn't be mixing jquery and Vue if it's not 100% necessary.
Here a simple way to do it:
https://jsfiddle.net/gmmujLs4/2/
HTML
<div id="root">
<div class="navbar-start" v-for="tab in tabs">
<a class="navbar-item" href="#" #click="activeTabName = tab.name">{{tab.name}}</a>
</div>
<div class="card-content">
{{ coinInfo[activeTabName] }}
</div>
</div>
Vue instance
new Vue({
el: '#root',
data: {
activeTabName: 'Description',
tabs: [
{
name: 'Description',
},
{
name: 'Features',
},
{
name: 'Technology',
}
],
coinInfo: {
Description:'DescriptionContent',
Features:'FeaturesContent',
Technology:'TechnologyContent'
}
}
})
coinInfo could be passed by properties instead of beeing declared as data.

Vue Iteration inside element

I am attempting to iterate through an array from a Vue component without reapeating the element on which I call 'v-for'. I have not found a subsitute for 'v-for' in the official docs API, nor in articles online.
I have this:
<div v-for="(item, index) in items">
<foo :index="index">
<foo/>
</div>
Want this:
<foo :index="0"><foo/>
<foo :index="1"><foo/>
<foo :index="2"><foo/>
//etc...
And have tried this, which does not work:
<foo v-for="(item, index) in items":index="index">
<foo/>
Help is much appreciated! Started coding with Vue.js yesterday.
Your HTML is wrong. You can do this just fine.
<foo v-for="(item, index) in items" :index="index"></foo>
<foo v-for="(item, index) in items">
{{index}}
</foo>
Does this work for you http://jsbin.com/kapipezalu/edit?html,js,console,output
You probably forgot to define props into foo component.
<div id="app">
<foo v-for="(item, index) in items" :index="index"></foo>
</div>
<template id="foo-temp">
<div>
<span>{{ index }}</span>
</div>
</template>
JS:
Vue.component('foo', {
props: ['index'],
template: '#foo-temp'
})
new Vue({
el: '#app',
data: {
items: [
{id: 1, title: 'One'},
{id: 2, title: 'Two'},
{id: 3, title: 'Three'}
]
}
})
Pro Tip: Use Browser Console - It will show you some cool warnings and errors.

Vue2 error when trying to splice last element of object

I have a Vue2 app wit a list of items which I can choose and show, or delete.
When deleting the last element in the list (and only the last one) - I get Vue warn - "[Vue warn]: Error when rendering root instance: "
my HTML:
<body >
<div id="app">
<ul>
<li v-for="(item, index) in list" v-on:click = "selectItem(index)" >
<a>{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
</ul>
<div>
<span>{{selectedItem.name}}</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
The JS:
var app = new Vue({
el: '#app',
data: {
index: 0,
selectedItem: {},
list : [
{ id: 1, name: 'org1', desc: "description1"},
{ id: 2, name: 'org2', desc: "description2"},
{ id: 3, name: 'org3', desc: "description3"},
{ id: 4, name: 'org4', desc: "description4"}
]
},
methods: {
deleteItem: function(index) {
this.list.splice(index,1);
},
selectItem: function(index) {
this.selectedItem = this.list[index];
},
}
})
Can you please advise why does this happen and how to solve this issue?
The problem is happening as you have having selectItem bind at li level, so event when you click cross button, selectItem gets executed and that same item gets deleted as well, causing this error.
One way to solve this problem can be moving the selectItem binding inside li as follows
<li v-for="(item, index) in list">
<a v-on:click = "selectItem(index)" >{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
See working fiddle.
Another approach can be when printing selectedItem.name in your HTML, you put a null check, whether selectedItem exist or not like following:
<span>{{selectedItem && selectedItem.name}}</span>
See Working fiddle.

Categories

Resources