Easy VueJS and Vuex Pagination

Pagination in web development is the process of separating the data we display into a number of pages. Since displaying a large amount of data on a single page is both inconvenient for the user and performance heavy. If you’re using VueJS and Vuex, you might be wondering, how to paginate data with VueJs and Vuex?

With Vuex we can simply paginate data by doing specific requests to our server with pagination options and managing these options in the store. Let’s find out how!

Creating a Vuex Store Module

First of all, we will start by creating our Vuex module.

Modules in Vuex are used to split the store into separate sub-stores, which allows for separation of concerns and easier management.

We will store each module in a dedicated folder with the module name, so let’s create a store/employees/state.js file:

export const EMPLOYEES = 'data'
export const PAGINATION = 'pagination'

export default {
  [EMPLOYEES]: [],
  [PAGINATION]: {
    page: 1,
    limit: 10,
    totalPages: 0,
  },
}

We will store the data in the data array and use the pagination object to manage the pagination options. For the pagination options we will have the current page, the number of items per page(limit) and the number of pages, which will help with displaying the pagination controls in the UI later.

Then, we will import this file inside store/employees/index.js:

import state from './state'

export const EMPLOYEES_MODULE = 'employees'

export default {
  namespaced: true,
  state,
}

export * from './state'

There are few things going on here, first of all, you can see that we imported the state and exported it again, this will allow us to import the state constants directly from @/store/employees rather than @/store/employees/state therefore it’s easier.

Also, we have exported an object with the state and namespaced set to true which will make the module self-contained and will not register the mutations, actions or getters into the global store.

Finally, all we have left to do is to register this module in our store main file, store/index.js:

import Vue from 'vue'
import Vuex from 'vuex'

import employees, { EMPLOYEES_MODULE } from './employees'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    [EMPLOYEES_MODULE]: employees,
  },
})

Now if we run our app using npm run serve, go to the vue-devtools and check the Vuex store, we should see our state inside the employees object:

vuex store state data

Fetching data with Axios

We will need an API to test our frontend code. So I have created a basic express app here. You can clone it and then run node app which will start the server on port 3000.

Furthermore, we will create a services folder that will contain an api.js file. In this file, we will create a new Axios instance with a base URL pointing to our API. Then we create a method to fetch the data from the employees endpoint:

import { create as createAxiosInstance } from 'axios'

const BASE_URL = 'http://localhost:3000/api/v1/'
const instance = createAxiosInstance({
  baseURL: BASE_URL,
})


export default {
  async fetchEmployees() {
    const endpoint = 'employees'

    return (await instance.get(endpoint)).data
  },
}

Finally, we will support pagination by supplying the pagination options as a parameter. And then adding it to the URL as a query string:

import { create as createAxiosInstance } from 'axios'

// ...code

export default {
  async fetchEmployees(options) {
    let endpoint = 'employees'

    if (options.page) {
      endpoint = `${endpoint}?page=${pagination.page}`
    }

    if (pagination.limit) {
      endpoint = `${endpoint}&limit=${pagination.limit}`
    }

    return (await instance.get(endpoint)).data
  },
}

Saving Data in the Store

We need a way to fetch the data from our module and save it in the state, we can do that by creating actions and mutations. Let’s start with the mutations.

First, we will create a store/employees/mutations.js with two mutations, one for saving the data and the second for setting our pagination options:

export const SET_PAGINATION = 'set_pagination'
export const SET_DATA = 'set_data'

export default {
  [SET_PAGINATION](state, pagination) {
    state.pagination = pagination
  },

  [SET_DATA](state, data) {
    state.data = data
  },
}

Pretty straight forward, we’re just assigning new data to the state properties.

Furthermore, we will create our store/employees/actions.js file which will have the load employees action:

import api from '@/services/api'
import {
  SET_DATA,
  SET_PAGINATION,
} from './mutations'
import state from './state'

export const FETCH_EMPLOYEES = 'load_employees'

export default {
  async [FETCH_EMPLOYEES]({ commit }, payload) {
    const data = await api.fetchEmployees({
      ...state.pagination,
      ...payload,
    })

    commit(SET_DATA, data.data)
    commit(SET_PAGINATION, {
      page: data.page,
      limit: data.limit,
      totalPages: data.totalPages,
    })
  },
}

The action uses the API to fetch the employees, it accepts a payload which is the pagination options. We merge the pagination options with the payload so that we can provide one option at a time when we want to.

After fetching the employees we commit the two mutations we created earlier. The first one is setting the employees data, and the second one is setting the pagination data. We will rely on the pagination data from the server since it will contain the number of pages we can have.

Displaying data and pagination controls

Now, all we have left is displaying the data and the pagination. Let’s start with the data.

Since we’re not going to do any transformation or some complex logic. We will just get the data from the state directly without using getters.

Let’s create a new src/components/Employees.vue where we will first execute the fetch employees action when the component is created:

<template>
  <div></div>
</template>

<script>
import { mapActions } from 'vuex'

import {
  EMPLOYEES_MODULE,
  FETCH_EMPLOYEES,
} from '@/store/employees'

export default {
  created() {
    this.fetchEmployees({ page: 1, limit: 20 })
  },

  methods: {
    ...mapActions(EMPLOYEES_MODULE, {
      fetchEmployees: FETCH_EMPLOYEES,
    }),
  },
}
</script>

We used the mapAction method to map the action from the store module, employees, into our components methods, then we executed it in the created lifecycle hook.

Now we will use the mapState method to map the state we have into the component computed properties. Then, we will loop over the employees array and display them in a list.

<template>
  <div>
    page {{ pagination.page }}
    <ul>
      <li
        v-for="employee in employees"
        :key="employee.id"
      >
      {{ employee.name }}
      </li>
    </ul>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex'

import {
  EMPLOYEES_MODULE,
  FETCH_EMPLOYEES,
  EMPLOYEES,
  PAGINATION,
} from '@/store/employees'

export default {
  //...code

  computed: {
    ...mapState(EMPLOYEES_MODULE, {
      employees: EMPLOYEES,
      pagination: PAGINATION,
    }),
  },

  //...code
}
</script>

We will also display the pagination controls and disable them depending on the data we have in the pagination state

<template>
  <div>
    page {{ pagination.page }}
    <ul>
      <li
        v-for="employee in employees"
        :key="employee.id"
      >
      {{ employee.name }}
      </li>
    </ul>
    <button
      @click="previousPage"
      :disabled="pagination.page === 1"
    >
      Previous Page
    </button>
    <button
      @click="nextPage"
      :disabled="pagination.page === pagination.totalPages"
    >
      Next Page
    </button>
  </div>
</template>

<script>
//...code

export default {
  //...code
  methods: {
    ...mapActions(EMPLOYEES_MODULE, {
      fetchEmployees: FETCH_EMPLOYEES,
    }),
    nextPage() {
      this.fetchEmployees({
        page: this.pagination.page + 1,
      })
    },
    previousPage() {
      this.fetchEmployees({
        page: this.pagination.page - 1,
      })
    },
  },
}
</script>

Now we will implement the nextPage and previousPage methods that will just call the fetchEmployees action with the next or previous page number

And that’s it, we have a working, basic pagination example: