How to Build an Infinite Scroll with Vue.js

How to Build an Infinite Scroll with Vue.js

An infinite scroll plugin for Vue. When there is only 1 page or we reach the last page, the $state. complete() will be called to stop the request and show either of the slots otherwise $state

I was recently assigned to as task to paginate our chat app and other pages that requires pagination. Pagination was great but we want to try something new and in our chat app it is a big no no.

So the solution is to create an infinite scroll, which is very common now and you can see this in popular chat applications like facebook messenger.

I decided to and it was approved by the team to use a third party so it will be fast and easy to achieve it and found this Vue Infinite Loading.

Let’s do it!

Install first Vue Infinite Loading using

npm i vue-infinite-loading -S 

Then the implementation will look like this:

User.vue

<template>
  <div>
  <ol>
    <li v-for="user in users" :key="user.id"></li>
  </ol>

  <infinite-loading spinner="bubbles" @infinite="infiniteHandler">
      <div class="text-red" slot="no-more">No more users</div>
      <div class="text-red" slot="no-results">No more users</div>
  </infinite-loading>
  </div>
</template>
<script>
export default {
    data: function() {
        return {
            page: 2,
            lastPage: 0,
            isInit: true,
            users: {},
        }
    },

    created: function() {
      this.fetchUsers()
          .then(response => {
            if (response.data.users.length > 0) {
              this.users = response.data.users;
              this.isInit = false;
            }else{
              console.log('No users found.');
            }
          })
          .catch(e => console.log(e))
    },
    methods: {
      fetchUsers: function() {
        let url = isInit 
                ? axios.get('api/users')
                : axios.get(`api/users?page=${this.page}`);

        return axios.get(url);
      },

      infiniteHandler: function($state) {
          setTimeout(function () {
            this.fetchUsers()
                .then(response => {
                    if (response.data.users.length > 0) {
                      this.lastPage = response.data.pagination.last_page;
                      response.data.users.forEach(message => {
                        this.messages.push(message);
                      });
                      if (this.page -1 === this.lastPage) {
                        this.page = 2;
                        $state.complete();
                      } else {
                        this.page += 1;
                      }
                      $state.loaded();
                    } else {
                      this.page = 2;
                      $state.complete();
                    }
                })
                .catch(e => console.log(e));
            }.bind(this), 1000);
      }
    },
}
</script>

components/User.vue

Then we can use the infinite-loadingin the component where we want to load the data, on this example the User component. And customized the 2 slots status messages for no-more and no-results, which you can add more by the way using this guide.

Then attached theinfiniteHandler method to fetch the remaining users. When there is only 1 page or we reach the last page, the$state.complete() will be called to stop the request and show either of the slots otherwise $state.loaded() is called to continue load more users when a user scroll at the bottom.

Making It Reusable

You can refactor this to become a reusable which in my case by creating a mixins together with my vuex store module by calling the this.$store.commit(type.toUpperCase(), response.data[type]);.

The mixin will look like this: infinite-scroll.js

export default {
    data: function() {
        return {
            page: 2,
            lastPage: 0,
        }
    },

    methods: {
        infiniteHandler: function(state, type) {
            setTimeout(function () {
                axios.get(`${type}?page=${this.page}`)
                .then(response => {
                    if (response.data[type].length > 0) {
                        this.lastPage = response.data.pagination.last_page;

                        this.$store.commit(type.toUpperCase(), response.data[type]);

                        if (this.page -1 === this.lastPage) {
                            this.page = 2;
                            state.complete();
                        } else {
                            this.page += 1;
                        }

                        state.loaded();
                    } else {
                        this.page = 2;
                        state.complete();
                    }
                })
                .catch(e => console.log(e));
            }.bind(this, state), 1000);
        },

    },
}

mixins/infinite-scroll.js

And the example implementations:

User.vue

<template>
  <div>
    <ol>
      <li v-for="user in users" :key="user.id"></li>
    </ol>

    <infinite-loading spinner="bubbles" @infinite="fetchUsers">
        <div class="text-red" slot="no-more">No more users</div>
        <div class="text-red" slot="no-results">No more users</div>
    </infinite-loading>
  </div>
</template>
<script>
import { mapGetters } from 'vuex';
import { infiniteScroll } from '../mixins';
export default {
  mixins: [infiniteScroll],

  created: function() {
    this.$store.dispatch('fetchUsers');
  },

  computed: {
        ...mapGetters({
            'users': 'USERS'
        }),
    },
  methods: {
    fetchUsers: function($state) {
       this.infiniteHandler($state,'users');
    }
  },
}
</script>

components/User.vue

And

Post.vue

<template>
  <div>
    <ol>
      <li v-for="post in posts" :key="post.id"></li>
    </ol>

    <infinite-loading spinner="bubbles" @infinite="fetchPosts">
        <div class="text-red" slot="no-more">No more users</div>
        <div class="text-red" slot="no-results">No more users</div>
    </infinite-loading>
  </div>
</template>
<script>
import { mapGetters } from 'vuex';
import { infiniteScroll } from '../mixins';
export default {
  mixins: [infiniteScroll],

  created: function() {
    this.$store.dispatch('fetchPosts');
  },

  computed: {
        ...mapGetters({
            'users': 'POSTS'
        }),
    },
  methods: {
    fetchPosts: function($state) {
       this.infiniteHandler($state,'posts');
    }
  },
}
</script>

components/Post.vue

Multiple infinite loading

I encountered a bug while using it in our chat app that when a user clicks on the thread of messages then clicks on another thread, the messages of the first clicked thread will also be displayed on the other opened thread. It took me hours to fix it as I thought I was having this bug on my inifiniteHandler method but it turns out that it was an instance problem. So to have a multiple instances, you add an identifier and make sure it is dynamic:

<infinite-loading :identifier="post.id"></infinite-loading>

Scroll to top

If you want to change the scrolling to make it the same with the popular chat app like facebook messenger, you can follow this guide.

vuejs javascript vue vue-js

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

8 Popular Websites That Use The Vue.JS Framework

In this article, we are going to list out the most popular websites using Vue JS as their frontend framework. Vue JS is one of those elite progressive JavaScript frameworks that has huge demand in the web development industry. Many popular websites are developed using Vue in their frontend development because of its imperative features.

Vue ShortKey plugin for Vue.js

Vue-ShortKey - The ultimate shortcut plugin to improve the UX .Vue-ShortKey - plugin for VueJS 2.x accepts shortcuts globaly and in a single listener.

A Vue.js wrapper component for Grid.js

A Vue wrapper component for Grid.js. Grid.js is a Free and open-source HTML table plugin written in TypeScript. It works with most JavaScript frameworks.

Vue.js image clipping Components using Vue-Rx

vuejs-clipper .Vue.js image clipping components using Vue-Rx. Add image clipping components to your Vue application in nothing flat. Touch devices supported and fully responsive.

Vue.js JWT Authentication with Vuex and Vue Router

Build a Vue.js JWT Authentication application using Vuex, Vue Router, VeeValidate - JWT authentication with Vue, Vuex, Vue Router that supports VeeValidate