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.
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-loading
in 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.
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
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>
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