Vue Warning on Console : You may have an infinite update loop in a component render function - vue.js

I'm a new student of Computer Science and I have a school project to do on Vue.js.
It is working, but it shows the following Warning:
[Vue warn]: You may have an infinite update loop in a component render function.
English is not my first language, so I'm sorry if I write anything wrong in advance, but I'll try to explain what I have to do on the project.
I have to create a Photo Grid/Album that shows no more than 3 photos on each line. Something like this:
Example of how it has to look
The code is like this:
<template>
<v-container>
<!-- 'v-container' est´substituindo ' div class="clubes-lista" '-->
<div class="grid-photo">
<v-container class="grey lighten-3">
<v-row
v-for="index in numberLines()"
:key="index"
class="grey lighten-3"
>
<v-col v-for="n in 3" :key="n" cols="4">
<v-card v-if="numberPhotos > checkInsertedAllPhotos()" outlined>
<v-img
src="https://i.pinimg.com/736x/96/28/b7/9628b7892fd0543782f53eeec4bae502.jpg"
alt="Bird Photo"
>
</v-img>
<div class="photo-subtitle">
<v-icon size="15">mdi-heart-outline</v-icon>
<p>The Cockatiel</p>
</div>
</v-card>
<v-card v-else></v-card>
</v-col>
</v-row>
</v-container>
</div>
</v-container>
<script>
export default {
name: "PhotoGrid",
data() {
return {
listPhotosOnGrid: [],
numberPhotos: 0,
numberTotalLines: 0,
countPhotos: 0,
};
},
computed: {},
methods: {
createPhotosList() {
var listPhotos = [];
for (var i = 0; i < 10; i++) {
var aux = {
id: i + 1,
src:
"https://i.pinimg.com/736x/96/28/b7/9628b7892fd0543782f53eeec4bae502.jpg",
subtitle: "The cockatiel",
};
listPhotos.push(aux);
}
this.listPhotosOnGrid = listPhotos;
this.numberPhotos = listPhotos.length;
},
numberLines() {
this.createPhotosList();
var photosMultipleThree = this.numberPhotos;
while (photosMultipleThree % 3 != 0) {
photosMultipleThree = photosMultipleThree + 1;
}
this.numberTotalLines = photosMultipleThree / 3;
return photosMultipleThree / 3;
},
checkInsertedAllPhotos() {
var cont = this.countPhotos++;
return cont;
},
},
};
<style scoped>
.photo-subtitle {
font-size: 10px;
display: flex;
flex-direction: row;
justify-content: left;
align-items: flex-start;
padding: 0;
margin-top: 10px;
}
.photo-subtitle p {
margin-left: 5px;
}
So... Trying to figure out what is happening, I think the Warning comes from the following line:
<v-card v-if="numberPhotos > checkInsertedAllPhotos()" outlined>
The if is because it can't show more images that what is given. For example, each line shows 3 photos, so if I don't have a total of photos that is multiple os 3, I don't want to fill all columns on the last line, only the necessary to show all the photos.
But I just think, based on some tests, I'm not sure if the Warning comes from this line.
The code works, but I feel something is not right. (It works partialy actually, because I still don't know how to get the info from the array to show, so it only loops the same thing)
I want to learn how to make it right.
So does anyone can tell me what I can do to make the Warning disappear or if I have to change everything, and make make a new logic for it to work properly.
I just want to learn.
Thank you so, so much and sorry again if this is a stupid question, or if I'm making everything wrong, hehe.
And sorry if I wrote anything wrong.
:)

I've had a look to your code. First I would recommend you to stop using the var keyword but use let or const instead (you may check the question here and have a look at the scope based on those keyword, the var scope is very unusual nowadays and it will lead you to some issues).
Regarding your particular issue of infinite update loop, the problem comes from your checkInsertedAllPhotos function. I would explain it like this :
In the v-if Vue internal mechanisms checks if the element should be rendered by triggering the function checkInsertedAllPhotos
When executing the function checkInsertedAllPhotos the data countPhotos is increased (by the way it would be better to just do
this.countPhotos ++; return this.countPhotos;
Since the Data has been modified Vue is triggering a re-render of the page
Since there is a re-render Vue checks in the v-if if the element should be rendered by triggering the function checkInsertedAllPhotos.
And it could be infinite if Vue doesn't prevent it !
I would suggest to re-work a little bit your logic indeed. However a quick solution for your problem would be that when you're checking an index in your list : for example if you want to display listPhotosOnGrid[i], you check if i<numberPhotos with a v-if and in the v-else you can display something else if you want.
I also think you could have a much simpler code, by leveraging css (for example css-grid). You can have something as simple as this :
<div v-for="n in 100">
<img v-bind:src="`https://i.pinimg.com/photos/${n}.png`">
</div>
and some css to style it and only have 3 items per rows. If you want to learn about css-grid I would recommend Mozzila documentation or the following tutoriel.
Right now I have a little trouble understanding the exact question, since there are multiples topics inside to solve but I hope my explanation on the infinite loop will help you !
Have fun while learning Vue !

Thank you everyone who helped me, and you sure helped me!!!!
I'm sorry I could only anwser today.
I think I was able to do much better on the code, I changed it a lot, specially the logic on the code. And I think it can get even better, but for sure I already made a big progress, hehe.
I was a bit reluctant of posting this question because even though that as the time that I posted it I wasn't sure of what to do, I kinda of had the feeling that was something stupid (like a stuid question), and that I would only be taking the time of anyone who stopped by my question. But I only got amazing answers, with everyone being so patient, so THANK YOU SO, SO MUCH!!
(I'm also new to StackOverflow, so I hope I'm making everything right, hehe)

Related

how to use vue-glide-js events on nuxt and load more slides on last one

i'm new on all of these so i need help. first of all how vue-glide-js events work to begin with. its documentation just listed them without examples.
And second and more important, i wanna send an axios request at the end of my carousel and load more items. i couldn't work with events so used active slide and watched it and on last slide sent and updated my data but slide still shows the previous one. what should i do?
it is the simplified version of my code
<template>
<div>
<vue-glide v-bind="carouselOptions" v-model="active">
<vue-glide-slide v-for="i in array" :key="i">
Slide {{ i }}
</vue-glide-slide>
</vue-glide>
<p>{{active}}</p>
</div>
</template>
<script>
export default {
data(){
return{
carouselOptions:{
direction: process.env.SITE_DIRECTION,
autoplay: false,
perView: this.$device.isMobileOrTablet ? 4 : 8,
peek: { before: 0, after: 50 }
},
array: [1,2,3,4,5,6,7],
active: 0
}
},
watch:{
active(){
console.log('active = ' + this.active)
if(this.active > this.array.length - 3){
this.array= [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
}
}
},
mounted(){
//
}
}
</script>
as you can see i manually added more items to my array but glide still show 7 items. i know it has something to do with mounting but dont know what to do.
and one more thing. if there is any better carousel that support rtl and breakpoint (items per view on different width) i would be happy to know. tanx
To use call a method at the end of slides just using #glide:run-end:
<template>
<div>
<vue-glide v-bind="carouselOptions" v-model="active" #glide:run-end="myMethod">
<vue-glide-slide v-for="i in array" :key="i">
Slide {{ i }}
</vue-glide-slide>
</vue-glide>
</div>
</template>
to show the new slides, it's a bit tricky! as far as i know the only way is to refresh (reload) the component! so put a key on vue-glide and change it after each new added slides. the only problem is, if there is images on slides, old ones also will be load again. (it's a suggestion and didn't tried my self yet, but maybe with a caching system, it may be solved)

Event handling after HTML injection with Vue.js

Vue is not registering event handler for HTML injected objects. How do I do this manually or what is a better way to work around my problem?
Specifically, I send a query to my server to find a token in text and return the context (surrounding text) of that token as it exists in unstructured natural language. The server also goes through the context and finds a list of those words that also happen to be in my token set.
When I render to my page I want all of these found tokens in the list to be clickable so that I can send the text of that token as a new search query. The big problem I am having is my issue does not conform to a template. The clickable text varies in number and positioning.
An example of what I am talking about is that my return may look like:
{
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
And the resulting output I am looking for is the sentence looks something like this in psuedocode:
When in the Course of <a #click='search("human events")'>human events</a>, it becomes necessary for <a #click='search("one people")'>one people</a> to dissolve the <a #click='search("political bands")'>political bands</a> which have connected
This is what I have tried so far though the click handler is not registered and the function never gets called:
<v-flex xs10 v-html="addlink(context.context, context.chunks)"></v-flex>
and in my methods section:
addlink: function(words, matchterms){
for(var index in matchterms){
var regquery = matchterms[index].replace(this.regEscape, '\\$&');
var query = matchterms[index];
var regEx = new RegExp(regquery, "ig");
words = words.replace(regEx, '<a href=\'#\' v-on:click.prevent=\'doSearch("'+ query +'")\'>' + query + '</a>');
}
return words;
}
As I said, this does not work and I know why. This is just showing that because of the nature of the problem is seems like regex is the correct solution but that gets me into a v-html injection situation. Is there something I can do in Vue to register the event handlers or can some one tell me a better way to load this data so I keep my links inline with the sentence and make them functional as well?
I've already posted one answer but I've just realised that there's a totally different approach that might work depending on your circumstances.
You could use event delegation. So rather than putting click listeners on each <a> you could put a single listener on the wrapper element. Within the listener you could then check whether the clicked element was an <a> (using event.target) and act accordingly.
Here's one way you could approach it:
<template>
<div>
<template v-for="segment in textSegments">
<a v-if="segment.link" href="#" #click.prevent="search(segment.text)">
{{ segment.text }}
</a>
<template v-else>
{{ segment.text }}
</template>
</template>
</div>
</template>
<script>
export default {
data () {
return {
"context": "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected",
"chunks": ['human events', 'one people', 'political bands']
}
},
computed: {
textSegments () {
const chunks = this.chunks
// This needs escaping correctly
const re = new RegExp('(' + chunks.join('|') + ')', 'gi')
// The filter removes empty strings
const segments = this.context.split(re).filter(text => text)
return segments.map(segment => {
return {
link: segment.match(re),
text: segment
}
})
}
},
methods: {
search (chunk) {
console.log(chunk)
}
}
}
</script>
I've parsed the context text into an array of segments that can then be handled cleanly using Vue's template syntax.
I've used a single RegExp and split, which will not discard matches if you wrap them in a capture group, (...).
Going back to your original example, v-html only supports native HTML, not Vue template syntax. So you can add events using onclick attributes but not #click or v-on:click. However, using onclick wouldn't provide easy access to your search method, which is scoped to your component.

Vue v-model input change mobile chrome not work

If i open https://v2.vuejs.org/v2/guide/forms.html#Text and edit text - no effect on typing text in mobile chrome. #keyup #input #keypress - v-model does not change when I'm typing
<input v-model="message" #keyup="log" placeholder="Edit">
<p>Edited: {{ message }}</p>
How can i fix it? I need get input value on typing (#keyup #input)
Update: After a lot of discussion, I've come to understand that this is a feature, not a bug. v-model is more complicated than you might at first think, and a mobile 'keyboard' is more complicated than a keyboard. This behaviour can surprise, but it's not wrong. Code your #input separately if you want something else.
Houston we might have a problem. Vue does not seem to be doing what it says on the tin. V-model is supposed to update on input, but if we decompose the v-model and code the #input explicitly, it works fine on mobile. (both inputs behave normally in chrome desktop)
For display on mobiles, the issue can be seen at...
https://jsbin.com/juzakis/1
See this github issue.
function doIt(){
var vm = new Vue({
el : '#vueRoot',
data : {message : '',message1 : ''}
})
}
doIt();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id='vueRoot'>
<h1>v-model</h1>
<div>
<input type='text'
v-model='message'
>
{{message}}
</div>
<h1>Decomposed</h1>
<div>
<input type='text'
:value='message1'
#input='evt=>message1=evt.target.value'
>
{{message1}}
</div>
</div>
I tried all solutions I could find on the internet, nothing worked for me. in the end i came up with this, finally works on android!
Trick is to use compositionupdate event:
<input type="text" ... v-model="myinputbox" #compositionupdate="compositionUpdate($event)">
......
......
methods: {
compositionUpdate: function(event)
{
this.myinputbox = event.data;
},
}
Ok, I dont know if there is another solution for this issue, but it can be solved with a simple directive:
Vue.directive('$model', {
bind: function (el, binding, vnode) {
el.oninput = () => (vnode.context[binding.expression] = el.value)
}
})
using it just like
<input v-$model="{toBind}">
There is an issue on the oficial repo, and they say this is the normal behavior (because the composition mode), but I still need the functionality
EDIT: A simpler solution for me was to just use #input.native. Also, the this event has (now?) a isComposing attribute which we can use to either take $event.data into account, or $event.target.value
In my case, the only scheme that worked was handling #keydown to save the value before the user action, and handling #keyup to process the event if the value had changed. NOTE: the disadvantage of this is that any non-keyboard input (like copy/paste with a mouse) will not work.
<md-input
v-else
:value="myValue"
ref="input"
#keydown="keyDownValue = $event.target.value"
#keyup="handleKeyUp($event)"
#blur="handleBlur()"
/>
With handleKeyUp in my case being:
handleKeyUp(evt){
if(evt.target.value !== this.keyDownValue){
this.$emit('edited', evt);
}
}
My use case was the following:
I requested a search endpoint in the backend to get suggestions as the user typed. Solutions like handling #compositionupdate lead to sending several several requests to the backend (I also needed #input for non-mobile devices). I reduced the number of requests sent by correctly handling #compositionStarted, but there was still cases where 2 requests were sent for just 1 character typed (when composition was left then, e.g. with space character, then re-entered, e.g. with backspace character).

Vuejs: 100vh on mobile browsers

I have page with min-height: 100vh
and it renders on mobile browsers with some overflow on bottom. And I use this script to fix it:
methods: {
calcVH() {
const vH = Math.max(document.documentElement.clientHeight, window.innerHeight, window.screen.height || 0)
document.getElementById('app').style.height = vH + 'px';
}
},
mounted() {
this.calcVH();
window.addEventListener('onorientationchange', this.calcVH, true);
window.addEventListener('resize', this.calcVH, true);
}
It works ok in emulator, but it doesn't work on chrome/safari mobile.
Did anyone have same problem?
Yes, I had similar issues using vh. It's a known problem.
My suggestion for you is to stop using vh on mobile and tablets in order to avoid these kind of hacks around. Use classic relative % (percentage) values instead. Since I've replaced vh with % I have no such problems on mobiles but it requires a bit more implementation effort. Using % isn't straightforward in all cases, but it pays you back since you've got a solution which works pretty everywhere in the same predictable way.
This VueJS component is designed to solve it:
https://github.com/razumnyak/vue-div-100vh
<template>
<vue100vh :css="{height: '100rvh';}">
<marquee>Your stuff goes here</marquee>
</vue100vh>
</template>
<script>
import vue100vh from 'vue-100vh'
export default {
components: { vue100vh },
}
</script>
Works for smaller percentages ... where rvh = "real viewport height".
<vue100vh :style="{ minHeight: '50rvh' }">
<marquee>This is inside a div that takes at least 50% of viewport height.</marquee>
</vue100vh>

Aurelia: Deleting array elements when changed to empty value

I have an array of strings bound to input elements:
<div repeat.for="i of messages.length">
<input type="text" value.bind="$parent.messages[i]">
</div>
I need to delete an element when the input content is deleted, without using dirty-checking.
This sounds easy - just delete the element which has empty value from the input.delegate handler, unfortunately this does not work due to an Aurelia bug #527. Here's a gist that tries this approach: https://gist.run/?id=e49828b236d979450ce54f0006d0fa0a
I tried to work around the bug by using queueTask to postpone deleting the array element, to no avail. And since the devs closed the bug because according to them it is a duplicate of a completely unrelated issue I guess it is not getting fixed anytime soon.
I am out of ideas how to implement this, so any suggestions are welcome.
Absolutely no need for any kind of dirty checking here! :)
Here's a working demo for your scenario: https://gist.run/?id=20d92afa1dd360614147fd381931cb17
$parent isn't needed anymore. It was related to pre-1.0 Aurelia versions.
If you use a variable instead of array indexes, you can leverage two-way data-binding provided by the input.
<template>
<div repeat.for="msg of messages">
<input type="text" value.bind="msg" input.delegate="onMessageChanged(msg, $index)">
</div>
</template>
So, your onChange event could be simplified like this:
msg holds the actual value of your current input.
i index will be used for deletion.
export class App {
messages = ['Alpha','Bravo','Charlie','Delta','Echo'];
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);
}
}
}
There was a related question about a similar problem. This answer might give you more details about the main idea.
Ok, so the solution to this is not to use the buggy (in this case) aurelia 2-way binding, but to use 1-way binding and set the value from the input.delegate handler:
https://gist.run/?id=2323c09ec9da989eed21534f177bf5a8
The #marton answer seems to work at first sight, but it actually disables 2-way binding, so any changes to the inputs are not copied to the array. But it gave me an important hint how to solve the issue.
The equivalent of this html code:
<div repeat.for="msg of messages">
<input type="text" value.bind="msg">
</div>
is this:
for (let msg of messages) {
msg = 'something else'; // note: this does not change the contents of the array
}
See issue #444 for more details
Hence, this forces one-way binding. To fix this in the #marton solution, we only have to change the value from the input.delegate handler:
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);//delete the element
}
else {
this.messages[i] = msg;//change the value
}
}