usePagination: 3 lines of code, infinite possibilities

Published: (June 5, 2026 at 07:05 AM EDT)
5 min read
Source: Dev.to

Source: Dev.to

Paginated lists are one of the most common patterns in frontend development. A hand-written implementation — with state management for page, pageSize, total, loading, error, and handlers for pagination, search debounce, and item mutations — easily reaches 50+ lines. alova’s usePagination hook collapses this into roughly 3 lines of configuration. This article examines how the abstraction works and where it fits. Here’s a standard paginated list written with Vue 3 and Axios: const list = ref([]); const loading = ref(false); const error = ref(null); const page = ref(1); const pageSize = ref(10); const total = ref(0); const searchKeyword = ref(”);

const fetchList = async () => { loading.value = true; error.value = null; try { const res = await axios.get(‘/api/users’, { params: { page: page.value, pageSize: pageSize.value, keyword: searchKeyword.value, }, }); list.value = res.data.data; total.value = res.data.total; } catch (e) { error.value = e.message; } finally { loading.value = false; } };

const changePage = (p) => { page.value = p; fetchList(); }; const changePageSize = (ps) => { pageSize.value = ps; page.value = 1; fetchList(); }; const onSearch = (keyword) => { searchKeyword.value = keyword; page.value = 1; fetchList(); }; const deleteItem = async (id) => { await axios.delete(/api/users/${id}); fetchList(); };

onMounted(() => fetchList());

The actual business logic — GET /api/users — is one line. The remaining 50+ lines are infrastructure: state wiring, loading toggles, pagination coordination, and filter resets. This pattern repeats in every list page across the project. usePagination internalizes all of that infrastructure: import { usePagination } from ‘alova/client’;

const searchKeyword = ref(”);

const { loading, data, error, page, pageSize, total, pageCount, isLastPage, fetching, removing, replacing, status, refresh, insert, remove, replace, reload, onSuccess, onError, onComplete, } = usePagination( (page, pageSize) => alovaInstance.Get(‘/api/users’, { params: { page, pageSize, keyword: searchKeyword.value }, }), { initialPage: 1, initialPageSize: 10, watchingStates: [searchKeyword], debounce: 300, } );

Three lines of configuration replace 50+ lines of imperative code. Here’s what you get: Modifying page.value or pageSize.value triggers a request automatically. Changing pageSize resets to page 1 — no manual wiring needed. page.value = 3; // fetch page 3 automatically pageSize.value = 20; // reset to page 1 and fetch automatically

Add reactive states to watchingStates with an optional debounce, and the list re-fetches whenever any filter changes: searchKeyword.value = ‘John’; // auto-fetch from page 1 after 300ms debounce

insert, remove, and replace update the local list and sync with the server — no manual refetch needed: await insert({ id: 99, name: ‘New User’ }, 0); // prepend await remove(2); // delete 3rd item await replace({ id: 5, name: ‘Updated’ }, 4); // replace 5th item await refresh(page.value); // force-refresh current page await reload(); // clear and reload from page 1

Next and previous pages preload in the background by default. When the user clicks “next page,” data is already in cache — no loading spinner. Beyond a single loading boolean, usePagination exposes per-operation states: loading, // current page is loading fetching, // preloading in background (doesn’t block the UI) removing, // array of row indices currently being removed replacing, // index of the row being replaced status, // current operation: “loading” | “removing” | “inserting” | “replacing”

This enables row-level loading indicators during delete operations while keeping the rest of the list interactive. The code reduction comes from elevating the abstraction level: Manual approach: You handle each HTTP request individually, managing all state transitions by hand usePagination approach: The entire “paginated list” scenario is a single configurable unit, with common logic (loading toggles, error handling, pagination coordination) baked into the hook Standard CRUD admin panels: User lists, order tables, content management — pages with pagination, search, and inline CRUD Multi-filter + pagination combos: Several filter dimensions that must reset pagination on change Frequent list item mutations: Inline edit, delete, insert operations where optimistic updates eliminate refetch overhead Standard API response shapes: { data: [], total: number } or structures configurable via data/total callbacks Cursor-based pagination: APIs using after/before cursors don’t map cleanly to the page-number model Bidirectional infinite scroll: UIs that load in both directions (e.g., chat histories) exceed the hook’s design scope Multi-list coordination: One operation must update several paginated lists with strict ordering requirements Multi-endpoint data aggregation: List data assembled from multiple APIs that can’t be mapped through data/total callbacks

Dimension Manual usePagination

Code volume 50-60 lines ~3 lines config

Pagination logic Manual page + fetch Auto-response to state changes

Search debounce Hand-rolled

watchingStates + debounce

List mutations Refetch after each operation Optimistic insert/remove/replace

Preloading Build from scratch Built-in, enabled by default

Loading granularity Single boolean Multi-level: loading/fetching/removing/replacing

Customization ceiling Fully flexible Constrained by hook design

Learning curve Framework fundamentals Understanding hook config and behavior

usePagination doesn’t do anything you couldn’t write yourself. Its value is packaging a battle-tested pagination implementation so you don’t write — and debug — the same 50 lines for every list page. For standard pagination use cases, the reduction in boilerplate and the built-in preloading and optimistic updates are measurable improvements. For scenarios outside its design scope, a custom implementation remains the clearer choice.

0 views
Back to Blog

Related posts

Read more »

Mobile Midsommer Madness

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...