I have a strange vuejs effect, when I add new object data, v-for re-renders all objects, even if it has already been rendered.
I am implementing infinite scrolling like face book.
To explain this code, I get new data from firebase and then push it into a data object when it reaches the bottom of the screen
var vueApp = new Vue({ el: '#root', data: { postList: [], isOkaytoLoad: false, isRoomPostEmpty: false, }, mounted: function() { // Everytime user scroll, call handleScroll() method window.addEventLis tener('scroll', this.handleScroll); }, methods: { handleScroll: function() { var d = document.documentElement; var offset = d.scrollTop + window.innerHeight; var height = d.offsetHeight - 200; // If the user is near the bottom and it's okay to load new data, get new data from firebase if (this.isOkaytoLoad && offset >= height) { this.isOkaytoLoad = false; (async()=>{ const lastPost = this.postList[this.postList.length - 1]; const room_id = PARAMETERS.get('room'); const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost)); const roomSnapshot = await getDocs(q); roomSnapshot.forEach((doc) => { const postID = doc.id; (async()=>{ // Put the new data at the postList object this.postList = [...this.postList, doc]; const q = query(collection(db, 'comments'), where('post_id', '==', postID)); const commentSnapshot = await getDocs(q); doc['commentCount'] = commentSnapshot.size; //this.postList.push(doc); console.log(this.postList); setTimeout(()=>{ this.isOkaytoLoad = true }, 1000); })(); }); })(); } } } })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div v-if="postList.length > 0" class="card-containers"> <!-- I have a component `Postcard` in my js file and it works well --> <Postcard v-for="post in postList" :key="post.id" :post-id="post.id" :owner-name="post.data().owner_displayName" :owner-uid="post.data().owner_id" :post-type="post.data().post_type" :image-url="post.data().image_url" :post-content="truncateString(linkify(post.data().post_content))" :room="post.data().room" :time="post.data().time.toDate()" :likers="post.data().likers" :comment-count="post.commentCount" :file-url="post.data().file_url" :file-name="post.data().file_name" :downloads="post.data().downloads"> </Postcard> </div>
Look at this screen recording, focused mouse, it's so laggy, I can't even click the buttons while vuejs is adding and loading new data
This is the code I use
I suspect that every time new data is added, VueJS re-renders all the data, causing this effect. How to force vueJS not to re-render data that has already been rendered on the screen?
You have two unnecessary asyncIIFE; The second one in
forEach
is particularly problematic because the asynchronous code in it will execute simultaneously on every loop iteration, This has the following effects:getDocs()
will fire immediately on every loop iteration, possibly spamming the server (assuming this is performing a network request). Is this your intention? It looks like you can only get a maximum of 5 new posts, so that's probably fine.Also do not use
var
; useconst
orlet
instead. There are almost no good reasons to usevar
anymore, let it die.I can't say this will significantly improve your performance, but I recommend the following changes (untested):
Executed in the template
:post-content="truncateString(linkify(post.data().post_content))"
meanslinkify
will be executed during every re-render . I suspectlinkify
might be slow for long lists? Can it be pre-calculated for each post in advance?When the component is installed, you are registering a window scroll event listener. If the component is destroyed, you need to unregister the event listener, otherwise it will still fire every time the window is scrolled. This may not be a problem in your case, but for reusable components it is necessary.