How to Build an Infinite Scroll with Vue.js

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

How to Build an Infinite Scroll with Vue.js
180.60 GEEK