pagination show wrong page of table
Describe the bug
I have a problem with the pagination of a table who get it's content from a provider function, which uses ctx.currentPage and ctx.perPage to limit the data.
I store the currentPage, perPage and totalRows in a localstorage whenever it changes using a watch function:
watch: {
perPage(newValue, oldValue) {
localStorage.setItem('perPage', JSON.stringify(newValue));
},
currentPage(newValue, oldValue) {
localStorage.setItem('currentPage', JSON.stringify(newValue));
},
totalRows(newValue, oldValue) {
localStorage.setItem('totalRows', JSON.stringify(newValue));
},
}
And restore it like this:
mounted() {
this.perPage = JSON.parse(localStorage.getItem('perPage')) || 10;
this.totalRows = JSON.parse(localStorage.getItem('totalRows')) || 0;
this.currentPage = JSON.parse(localStorage.getItem('currentPage')) || 1;
}
My provider function:
async provider(ctx) {
this.isBusy = true;
try {
return await itemsApi.fetchItems('https://domain.xyz/entrypoint', {
currentPage: ctx.currentPage,
perPage: ctx.perPage
}).then((result) => {
this.isBusy = false;
this.totalRows = result.totalRows;
return result.data;
});
} catch (error) {
this.isBusy = false;
this.totalRows = 0;
this.currentPage = 1;
return [];
}
},
The data section:
data() {
return {
isBusy: false,
perPage: 10,
currentPage: 1,
totalRows: 0,
}
}
The table / pagination markup:
<b-table
:items="provider"
:fields="filteredFields"
primary-key="uid"
:busy.sync="isBusy"
:current-page="currentPage"
:per-page="perPage"
/>
<b-pagination
first-number
last-number
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
/>
Every record has an edit link, who brings the user to a different url (it's not a single page application).
If the user goes back to the list page with the table, all limit settings will be restored from the localstorage correctly and the table also loads the correct section (e.g. records 11 - 20, if he/she was on page 2 before). Unfortunately the pagination always shows page 1 – indepenent from the value in currentPage. If the user clicks on page 2 of the pagination nothings happens, because he/she is already on page 2.
This problem is very annoying, especially if the table has a lot of pages and the user needs to come back to the previously visited page.
Steps to reproduce the bug
Like described
Expected behavior
The pagination should show the loaded currentPage
Versions
Libraries:
- BootstrapVue: 2.21.2
- Bootstrap: 4.6.0
- Vue: 2.6.14
Environment:
- Device: Mac
- OS: macOS Big Sur
- Browser: Firefox / Chrome / Edge / Safari
- Version: 89 / 91 / 91 / 14.1.1
Can you create a reproduction of the issue? You can export a template to Codesandbox or Codepen from the docs playground
Here is a sample code, but it doesn't show the problem, because i have no idea how to simulate the edit and back function in a code sandbox: https://codesandbox.io/s/uoxed
Everytime i reload the sandbox, the localstorage content get lost.
I found a way: https://mdg5u.csb.app/
To reproduce: open the page, switch to a page higher than one and reload page afterwards. The table will show the previous selected page, but the pagination always show page one.
Looks like it's because <b-pagination> hasn't properly gotten to calculate how many pages there should be available yet, so it wont allow a higher number than 1.
You can fix that by using $nextTick when setting the current page.
this.$nextTick(() => {
this.currentPage = JSON.parse(localStorage.getItem("currentPage")) || 1;
});
This works, but results in two requests to the data entry point.
- with currentPage: 1
- with currentPage restored from localStorage
I have added a console.log to the provider function to demonstrate this behaviour.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.
This issue still exists and is not solved.
It seems this issue still exists on v2.21.2
Any solution?
Okay, after a lot of trail & error with the framwork pagination components, I came up with my own.
If you want to disable reseting to 1, uncomment created, otherwise include a v-if to wait for the initial pages to be loaded.
Good luck
<template>
<nav>
<ul class="pagination justify-content-center">
<li class="page-item" :class="{ 'disabled': meta.current_page === 1 }">
<a class="page-link" href="#" aria-label="Previous" @click.prevent="switched(meta.current_page - 1)">
<span aria-hidden="true">«</span>
<span class="sr-only">Previous</span>
</a>
</li>
<template v-if="section > 1">
<li class="page-item">
<a class="page-link" href="#" @click.prevent="switched(1)">1</a>
</li>
<li class="page-item">
<a class="page-link" href="#" @click.prevent="goBackASection">...</a>
</li>
</template>
<li class="page-item" v-for="page in pages" :class="{ 'active': meta.current_page == page }" :key="page">
<a class="page-link" :href="getLink(page)" @click.prevent="switched(page)">{{ page }}</a>
</li>
<template v-if="section < sections">
<li class="page-item">
<a class="page-link" href="#" @click.prevent="goForwardASection">...</a>
</li>
<li class="page-item">
<a class="page-link" href="#" @click.prevent="switched(meta.last_page)">{{ meta.last_page }}</a>
</li>
</template>
<li class="page-item" :class="{ 'disabled': meta.current_page === meta.last_page }">
<a class="page-link" href="#" aria-label="Next" @click.prevent="switched(meta.current_page + 1)">
<span aria-hidden="true">»</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
</template>
<script>
import { range } from 'lodash'
export default {
props: [
'value',
'numberOfPages',
'meta',
'linkGen'
],
data () {
return {
numbersPerSection: 3
}
},
computed: {
section() {
return Math.ceil(this.meta.current_page / this.numbersPerSection)
},
sections() {
return Math.ceil(this.meta.last_page / this.numbersPerSection)
},
lastPage() {
let lastPage = ((this.section - 1) * this.numbersPerSection) + this.numbersPerSection
if (this.section == this.sections) {
lastPage = this.meta.last_page
}
return lastPage
},
pages() {
return range(
(this.section - 1) * this.numbersPerSection + 1,
this.lastPage + 1
)
}
},
methods: {
getLink(page) {
if (this.linkGen) {
let resolved = this.$router.resolve(this.linkGen(String(page)))
return resolved.href
}
return `?page=${page}`
},
switched(page) {
if (page <= 0 || page > this.meta.last_page) {
return
}
this.$emit('pagination:switched', page)
},
goBackASection() {
this.switched(
this.firstPageOfSection(this.section - 1)
)
},
goForwardASection() {
this.switched(
this.firstPageOfSection(this.section + 1)
)
},
firstPageOfSection (section) {
return (section - 1) * this.numbersPerSection + 1
}
},
created() {
if (this.meta.current_page > this.meta.last_page) {
this.switched(this.meta.last_page)
}
}
}
</script>
@rezaffm Just add an v-if to the b-pagination component, it will do the trick
<b-pagination
v-if="!loading && total > 0"
v-model="currentPage"
/>
Hi pal, thank you! That was not only my issue, I also wanted to use pagination in a classical (unopinionated way), e.g. without resetting to 1 when I change the number of pages - so I came up with my own. Might help anyone (or not). I will soon migrate to Vuetify.