Vuejs warning: infinite update loop in a component render function - javascript

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.

Related

v-model on input change is heavy on performance

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.

Mix Dynamic class with data binders in Vue.js

So I have the following v-for in a HTML:
<ul v-for="(item, index) in openweathermap.list">
<li>{{item.dt_txt}}</li>
<li>{{item.weather[0].description}}</li>
<li>{{item.weather[0].id}}</li>
<li>{{item.main.temp}}°C</li>
</ul>
What I want to do is to add an icon to these information, like font awesome.
So I found these: <i class="owf owf-200"></i> This will serve me just right but the number must change dynamically. So the number is the {{item.weather[0].id}} in the v-for.
My question is this; How can I mix these two together?
I tried something like this <i class="owf owf-{{item.weather[0].id}}"></i>
but it obviously has wrong syntax.
Any help will be greatly appreciated!
You can use the v-bind:class - which allows you to append two strings, just like in Javascript. So the value should be 'owf owf-' + item.weather[0].id.
In the snippet, I've done that with dummy data and color changes for two different classes, but you should get the idea.
var app = new Vue({
el: "#app",
data:{
items: [
{
weather: [{ id: 200 }],
txt: "Some text"
},
{
weather: [{ id: 300 }],
txt: "Some other text"
}
]
}
});
.owf.owf-200 {
color: red;
}
.owf.owf-300 {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<template v-for="item in items">
<span v-bind:class="'owf owf-' + item.weather[0].id">
{{ item.txt }}
</span>
<br />
</template>
</div>

can not insert vue-router tag inside link

I have a Vuejs app where I allow users to register and become members.
I added vuex to store messages related to success/failure of the process.
Currently, everything works except when I try to store and show a [vue-router] link inside a variable like this:
...
Registration() {
if(true) {
this.$store.commit('SET_MESSAGE', {
type: 'success',
title: 'Registration Success',
content: 'please click <router-link to="/profile"> here </router-link> to see your profile'
}
}
Now, I can retrieve all the properties and display them, but the <router-link to="/profile"> here </router-link> tag does not transform (or functions) as it is supposed to.
This is how I am displaying it.
<div class="alert alert-dismissible" :class="'alert-' +type" >
<h1> {{tilte}} </h1>
<p> {{content}} </p>
</div>
I tried with <p v-bind:html='content'></p> and {{{ content }}} the route does not work in either case
The double mustaches interprets the data as plain text, not HTML. In
order to output real HTML, you will need to use the v-html directive DOC
<p v-html="content" />
EDIT
In order to make router-link to work you need to use a computed property that return an object of components options:
computed: {
contentComp () {
return { template: `<p>${this.content}</p>` }
}
}
Then render it:
<component :is="contentComp"></component>
Final result:
const profile = {
template: '<div> profile page! </div>'
}
const router = new VueRouter({
routes: [{
path: '/profile',
component: profile
}]
})
new Vue({
router,
el: '#app',
data: {
title: 'Registration Success',
content: 'please click <router-link to="/profile"> here </router-link> to see your profile'
},
computed: {
contentComp() {
return {
template: `<p>${this.content}</p>`
}
}
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div>
<h1> {{title}} </h1>
<component :is="contentComp"></component>
</div>
<router-view></router-view>
</div>
You cannot insert dynamic content (that is to say content that needs to be compiled, like your router-link) with v-html, it's only meant for regular html. I suggest an alternate approach, that is to put your router-link in the template, but hide it with a v-if switch, that way it will be rendered and displayed only once you toggle the switch.
Template
<span v-if="displayRouter">
please click <router-link to="/profile"> here </router-link> to see your profile
</span>
JS
Registration() {
if(true) {
this.displayRouter = true
...
Generally I believe it's much clearer to keep all the html in the html, not in the JS.
Register () {
if(ok) {
this.$store.commit('SET_MESSAGE', {
type: 'success',
title: 'Registration Success',
content: 'please click here to see your profile'
}
}
You can use <a> tag.

How can I change data value from one component to another component in Vue Js?

I am new in Vue Js. So, I am facing a problem to changes data value from another component.
I have a component A:
<template>
<div id="app">
<p v-on:click="test ()">Something</p>
</div>
</template>
import B from '../components/B.vue';
export default {
components: {
B
},
methods: {
test: function() {
B.data().myData = 124
B.data().isActive = true
console.log(B.data().myData);
console.log(B.data().isActive);
}
}
}
Component B:
export default {
data() {
return {
myData: 123,
isActive: false
}
}
}
It still component B data.
But it cannot be affected component B data. I want to data changes of component B from component A. How can I do that?
Please explain me in details. I have seen vue js props attribute but I don't understand.
You're looking for Vuex.
It's the centralized store for all the data in your applications.
Take a look at their documentation, it should be pretty straightforward.
You can pass down props to the component B. These props can be updated by the parent component. You can think of B as a stupid component that just renders what the parent tells it to rendern. Example:
// Component A
<template>
<div id="app">
<p v-on:click="test ()">Something</p>
<b data="myData" isActive="myIsActive"></b>
</div>
</template>
<script>
import B from '../components/B.vue';
export default {
components: {
B
},
data() {
return {
myData: 0,
myIsActive: false,
};
},
methods: {
test: function() {
this.myData = 123
this.myIsActive = true
}
}
}
</script>
// Component B
<template>
<div>{{ data }}{{ isActive }}</div>
</template>
<script>
export default {
props: {
data: Number,
isActive: Boolean
};
</script>
There are few ways...
if your components have a parent child relationship you can pass data values from parent into child.
If your want to communicate back to parent component when child component has changed something, you can use vuejs event emitter(custom event) to emit a event when data value change and that event can be listened in another component and do what you want.
If your components doesn't have a relationship, then you have to use use something else than above things. You can use two things.one is event bus, other one is state management library.for vue there is a official state management library called VueX.it is very easy to use.if you want to use something else than vuex, you can use it such as redux, mobx etc.
This documentation has everything what you want to know. I don't want to put any code, because of doc is very clear.
VueX is the most preferable way to do this! Very easy to use..
https://v2.vuejs.org/v2/guide/components.html
//component A
Vue.component('my-button', {
props: ['title'],
template: `<button v-on:click="$emit('add-value')">{{title}}</button>`
});
Vue.component('my-viewer', {
props: ['counter'],
template: `<button>{{counter}}</button>`
});
new Vue({
el: '#app',
data: {
counter: 0,
},
methods: {
doSomething: function() {
this.counter++;
}
}
})
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
//parent
new Vue({
el: '#blog-post-demo',
data: {
posts: [{
id: 1,
title: 'My journey with Vue'
},
{
id: 2,
title: 'Blogging with Vue'
},
{
id: 3,
title: 'Why Vue is so fun'
}
]
}
});
Vue.component('blog-post2', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>`
})
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [{
id: 1,
title: 'My journey with Vue'
},
{
id: 2,
title: 'Blogging with Vue'
},
{
id: 3,
title: 'Why Vue is so fun'
}
],
postFontSize: 1
},
methods: {
onEnlargeText: function() {
this.postFontSize++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<p>Two components adding & viewing value</p>
<div id="app">
<my-button :title="'Add Value'" v-on:add-value="doSomething"></my-button>
<my-viewer :counter="counter"></my-viewer>
</div>
<br>
<br>
<p>Passing Data to Child Components with Props (Parent to Child)</p>
<div id="blog-post-demo">
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></blog-post>
</div>
<p>Listening to Child Components Events (Child to Parent)</p>
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post2 v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:enlarge-text="onEnlargeText"></blog-post2>
</div>
</div>
First, you need a parent so two component can communicate. when my-button component is clicked triggers an event add-value that calls doSomething() function, then updates the value & show it to my-viewer component.
HTML
<!--PARENT-->
<div id="app">
<!--CHILD COMPONENTS-->
<my-button :title="'Add Value'" v-on:add-value="doSomething"></my-button>
<my-viewer :counter="counter"></my-viewer>
</div>
VUE.JS
//component A
Vue.component('my-button',{
props:['title'],
template:`<button v-on:click="$emit('add-value')">{{title}}</button>`
});
//Component B
Vue.component('my-viewer',{
props:['counter'],
template:`<button>{{counter}}</button>`
});
//Parent
new Vue({
el: '#app',
data:{
counter:0,
},
methods:{
doSomething:function(){
this.counter++;
}
}
})
This is base on Vue Components Guide
Passing Data to Child Components with Props (Parent to Child)
VUE.JS
//component (child)
//Vue component must come first, else it won't work
Vue.component('blog-post', {
/*Props are custom attributes you can register on a component. When a
value is passed to a prop attribute, it becomes a property on that
component instance*/
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
//parent
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
});
HTML:
v-for will loop on posts and pass data to blog-post component
<div id="blog-post-demo">
<blog-post v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"></blog-post>
</div>
Listening to Child Components Events (Child to Parent)
HTML
You must first register the event by v-on:enlarge-text="onEnlargeText" to use $emit and make sure that it's always set to lower case or it won't work properly. example enlargeText and Enlargetext will always be converted to enlargetext, thus use enlarge-text instead, because its easy to read & valid, for a brief explanation about $emit you can read it here
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="onEnlargeText"></blog-post>
</div>
</div>
VUE.JS
When user clicks the button the v-on:click="$emit('enlarge-text')" will trigger then calling the function onEnlargeText() in the parent
//component (child)
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>`
})
//parent
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1
},
methods:{
onEnlargeText:function(){
this.postFontSize++;
}
}
})
Actually props suck sometimes you got some old external library in jquyer and need just damn pass value. in 99% of time use props that do job but.
A) spend tons of hours debuging changing tones of code to pass variables
B) one line solution
Create main variable in data letmeknow as object {}
this.$root.letmeknow
then somewhere in code from component
this.$root.letmeknow = this;
and then boom i got component console.log( this.$root.letmeknow ) and see now can change some values

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