Out-of-bounds page index during reactive dataset filtering in Vue.js
0 reputation · 04 Sept 2026, 13:52 UTC
0 reputation · 04 Sept 2026, 13:52 UTC
In a Vue.js application managing large datasets, a common pattern involves using computed properties to first filter a master array based on a query and then slice that filtered result for pagination. This ensures the UI remains reactive to both search inputs and page navigation.
A technical challenge arises when the underlying dataset is filtered while the user is positioned on a high page index. If a filter reduces the total number of available results to a count smaller than the current page offset, the computed slice may return an empty array despite results existing on earlier pages.
Given the reactive nature of Vue's state management, what is the standard approach to synchronize the current page index with the dynamic length of a filtered dataset? Should the reset logic reside within a watcher on the filtered computed property, or is there a more performant way to bound the query index?
When a filter reduces the number of items, the previously selected page index can become larger than the new total number of pages. Because the pagination component’s page prop retains its old value, the slice returns an empty array, appearing as an out‑of‑bounds error.
Vue’s reactivity updates any computed that depends on the filtered array. The pagination component (e.g., v-data-table, ElPagination, or a custom wrapper) must receive a page value that is always between 1 and totalPages, where totalPages = Math.ceil(filteredLength / pageSize).
const filteredItems = computed(() => {
return masterList.filter(item => item.name.includes(searchQuery));
});
const totalPages = computed(() => Math.max(1, Math.ceil(filteredItems.value.length / pageSize)));
watch(
() => filteredItems.value.length,
(newLen) => {
if (currentPage.value > totalPages.value) {
currentPage.value = 1;
}
}
);
<ElPagination
:page-size='pageSize'
:total='filteredItems.value.length'
v-model:current-page='currentPage'
@current-change='(page) => { currentPage.value = page; }'
/>
<ElPagination :key='filterVersion' … />
To confirm whether the watcher approach is necessary, please specify which pagination component you are using (e.g., Vuetify’s v-data-table, Element Plus’ ElPagination, Ant Design Vue’s a-pagination, or a custom component). Knowing the component’s API determines if a simple v-model binding suffices or if an explicit watcher is required.
Use comments to ask for clarification. Post a solution as an answer.
32,275 reputation · 04 Sept 2026, 17:56 UTC
One approach not yet mentioned: instead of watching the filtered array and mutating currentPage, derive a clamped page in a computed and use that everywhere for slicing and controls:
const totalPages = computed(() =>
Math.max(1, Math.ceil(filteredItems.value.length / pageSize))
);
const safePage = computed(() =>
Math.min(currentPage.value, totalPages.value)
);
const pagedItems = computed(() =>
filteredItems.value.slice(
(safePage.value - 1) * pageSize,
safePage.value * pageSize
)
);This keeps computeds pure — mutating currentPage inside a computed getter can trigger repeated evaluation or render loops. Two behavioral notes worth deciding on explicitly:
Math.max(1, ...) matters: with zero results, Math.ceil(0 / pageSize) is 0 and (safePage - 1) * pageSize goes negative without the floor.Also verify against your Vue major version: Vue 3 watchers default to flush: 'pre', so corrections land before re-render, while Vue 2's async watcher timing can show an empty page for a frame.