Boost Vue App Efficiency: A Reusable Pagination Hook Reduces 90% Repetitive Code

Learn how to eliminate repetitive pagination logic in Vue-based admin panels by using a custom usePageFetch hook that abstracts page state, loading, error handling, and caching, enabling developers to implement paginated data fetching with just a few lines of code and significantly improve development speed.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Boost Vue App Efficiency: A Reusable Pagination Hook Reduces 90% Repetitive Code
还在为每个列表页写重复的分页代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码

Background and Pain Points

In backend admin system development, pagination list queries are common, requiring handling of current page, page size, total count, loading and error states, search, refresh, page change, data caching and duplicate request handling. These repeated logics are scattered across components, making maintenance cumbersome.

To solve this, a pagination data management Hook is encapsulated, allowing pagination queries with only a few lines of code, saving time and reducing repetitive work.

Prerequisite – API Response Format

The query API should return data in the following format:

{
  "list": [
    // current page data array
    { "id": 1, "name": "user1" },
    { "id": 2, "name": "user2" }
  ],
  "total": 100 // total number of items
}

Demo: Pagination Query in a Few Lines

import usePageFetch from '@/hooks/usePageFetch' // pagination Hook
import { getUserList } from '@/api/user' // API method

const {
  currentPage,
  pageSize,
  total,
  data,
  isFetching,
  search,
  onSizeChange,
  onCurrentChange
} = usePageFetch(getUserList, { initFetch: false })

This way each pagination query only needs to import the hook and pass the request function, eliminating a lot of repetitive code.

Solution

Two cooperating Hooks are designed:

useFetch : basic request wrapper handling request state and caching.

usePageFetch : pagination logic wrapper managing pagination‑related state and operations.

usePageFetch (pagination layer)
├── Manage page / pageSize / total state
├── Handle search, refresh, page change logic
├── Unified error handling and user messages
└── Calls useFetch (request layer)
    ├── Manage loading / data / error state
    ├── Optional caching to avoid duplicate requests
    └── Success callback adaptation for different API formats

Core Implementation – useFetch

// hooks/useFetch.js
import { ref } from 'vue'

const Cache = new Map()

/**
 * Basic request Hook
 * @param {Function} fn - request function
 * @param {Object} options
 * @param {*} options.initValue - initial value
 * @param {string|Function} options.cache - cache config
 * @param {Function} options.onSuccess - success callback
 */
function useFetch(fn, options = {}) {
  const isFetching = ref(false)
  const data = ref()
  const error = ref()

  if (options.initValue !== undefined) {
    data.value = options.initValue
  }

  function fetch(...args) {
    isFetching.value = true
    let promise

    if (options.cache) {
      const cacheKey = typeof options.cache === 'function'
        ? options.cache(...args)
        : options.cache || `${fn.name}_${args.join('_')}`
      promise = Cache.get(cacheKey) || fn(...args)
      Cache.set(cacheKey, promise)
    } else {
      promise = fn(...args)
    }

    if (options.onSuccess) {
      promise = promise.then(options.onSuccess)
    }

    return promise
      .then(res => {
        data.value = res
        isFetching.value = false
        error.value = undefined
        return res
      })
      .catch(err => {
        isFetching.value = false
        error.value = err
        return Promise.reject(err)
      })
  }

  return { fetch, isFetching, data, error }
}

export default useFetch

Core Implementation – usePageFetch

// hooks/usePageFetch.js
import { ref, onMounted, toRaw, watch } from 'vue'
import useFetch from './useFetch'
import { ElMessage } from 'element-plus'

function usePageFetch(fn, options = {}) {
  const page = ref(1)
  const pageSize = ref(10)
  const total = ref(0)
  const data = ref([])
  const params = ref()
  const pendingCount = ref(0)

  params.value = options.params

  const { isFetching, fetch: fetchFn, error, data: originalData } = useFetch(fn)

  const fetch = async (searchParams, pageNo, size) => {
    try {
      page.value = pageNo
      pageSize.value = size
      params.value = searchParams

      await fetchFn({
        page: pageNo,
        pageSize: size,
        ...(searchParams ? toRaw(searchParams) : {})
      })

      data.value = originalData.value?.list || []
      total.value = originalData.value?.total || 0
      pendingCount.value = originalData.value?.pendingCounts || 0
    } catch (e) {
      console.error('usePageFetch error:', e)
      ElMessage.error(e?.msg || e?.message || 'Request error')
      data.value = []
      total.value = 0
    }
  }

  const search = async (searchParams) => {
    await fetch(searchParams, 1, pageSize.value)
  }

  const refresh = async () => {
    await fetch(params.value, page.value, pageSize.value)
  }

  const onSizeChange = async (size) => {
    await fetch(params.value, 1, size)
  }

  const onCurrentChange = async (pageNo) => {
    await fetch(params.value, pageNo, pageSize.value)
  }

  onMounted(() => {
    if (options.initFetch !== false) {
      search(params.value)
    }
  })

  watch(() => options.formRef, (formRef) => {
    if (formRef) {
      console.log('Form ref updated:', formRef)
    }
  })

  return {
    currentPage: page,
    pageSize,
    total,
    pendingCount,
    data,
    originalData,
    isFetching,
    error,
    search,
    refresh,
    onSizeChange,
    onCurrentChange
  }
}

export default usePageFetch

Full Usage Example (Element UI)

<template>
  <el-form :model="searchForm">
    <el-form-item label="Username">
      <el-input v-model="searchForm.username" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleSearch">Search</el-button>
    </el-form-item>
  </el-form>

  <el-table :data="data" v-loading="isFetching">
    <!-- table columns -->
  </el-table>

  <el-pagination
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :total="total"
    @size-change="onSizeChange"
    @current-change="onCurrentChange" />
</template>

<script setup>
import { ref } from 'vue'
import usePageFetch from '@/hooks/usePageFetch'
import { getUserList } from '@/api/user'

const searchForm = ref({ username: '' })

const {
  currentPage,
  pageSize,
  total,
  data,
  isFetching,
  search,
  onSizeChange,
  onCurrentChange
} = usePageFetch(getUserList, { initFetch: false })

const handleSearch = () => {
  search({ username: searchForm.value.username })
}
</script>

Advanced Usage – With Caching

const { data, isFetching, search } = usePageFetch(getUserList, {
  cache: (params) => `user-list-${JSON.stringify(params)}`
})

Design Rationale

Separation of Concerns : useFetch focuses on request state, usePageFetch on pagination logic.

Unified Error Handling : errors are handled centrally in usePageFetch.

Smart Caching : supports multiple caching strategies.

Lifecycle Integration : automatically fetches data on component mount.

Conclusion

The pagination management Hook boosts development efficiency, cutting repetitive code by up to 90%, provides comprehensive state management, caching, unified error handling, and is easy to extend with custom configurations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptVuePaginationHookuseFetchusePageFetch
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.