Tanstack Query / Vue Query - Pagination and Infinite Scroll Example In Vue JS
Overview
Tanstack Query / Vue Query offers a powerful combination for data management in Vue.js applications. Whether you're building a small project or a large-scale application, these libraries provide the tools and flexibility needed to handle complex data requirements. By incorporating these techniques into your projects, you can enhance user experiences and optimize data fetching, leading to more efficient and enjoyable applications.
This blog post is about an example application that shows how to use Tanstack Query / vue-query in an application to implement paging and infinite query/scrolling.
Vue Query / Tanstack Query for Vue will likely: Help you remove many lines of complicated and misunderstood code from your application and replace them with just a handful of lines of Vue Query logic.
Make your application more maintainable and easier to build new features without worrying about wiring up new server-state data sources
Have a direct impact on your end-users by making your application feel faster and more responsive than ever before.
Potentially help you save on bandwidth and increase memory performance
Please read about VueQuery and State Management Tools Like Vuex and Pinia. I believe that by using Tanstack Query, you can simplify some of your state management code in your vue applications
I am using Ionic Framework for the application user interface, but all of the concepts in this example can be used in a vue js application without the inclusion of Ionic Framework
Getting Started
$ npm i @tanstack/vue-query
# or
$ yarn add @tanstack/vue-query
Import the VueQueryPlugin
from the @tanstack/vue-query
package.
This plugin adds Vue Query functionality to the application, allowing for efficient data fetching and caching.
// main.ts
import { VueQueryPlugin } from '@tanstack/vue-query';
createApp(App)
.use(IonicVue, {
mode: 'ios',
})
.use(VueQueryPlugin)
.mount('#app');
💥 Sign Up For Our Newsletter - buff.ly/3lAk2jL
Application Components
The application has two primary components ExamplePaging.vue
and ExampleInfinite.vue
. They do basically what their name says which is to show an example of the two types of queries we discussed for the application
Paging - the ability to have a predefined page size and navigate forward and backward through the pages
Infinite - the ability to just continue to load data to the database will move a cursor through the content so you only load in the new data that you need
ExamplePaging.vue
Component
Several components and functions are imported from the @ionic/vue
and @tanstack/vue-query
packages; the useQuery
function from Vue Query is critical to what we are trying to accomplish in this section.
import { IonItem, IonLabel, IonList, IonButton, IonLoading } from "@ionic/vue";
import { Ref, ref } from "vue";
import { useQuery } from "@tanstack/vue-query";
The peopleFetcher function is defined, which takes a reactive variable page as a parameter. This function is an asynchronous function that fetches data from the Random User API based on the provided page value.
The function returns the fetched data or an empty array if the data is not available.
const peopleFetcher = async (page: Ref<number>) => {
const response = await fetch(
`https://randomuser.me/api/?page=${page.value}&results=20&seed=abc`
);
const data = await response.json();
// fake delay
await new Promise((resolve) => {
setTimeout(() => resolve(true), 1000);
});
return data?.results || [];
};
A reactive variable page
is created using the ref function from Vue; we set its value to 1
to start at the first page
The useQuery
function is passed properties
queryKey
is used as a unique identifier for the query.queryFn
property, which specifies the function responsible for fetching the data; it is set to use thepeopleFetcher
function mentioned above.keepPreviousData
property is set to true, which allows the previous data to be retained when fetching new data. This is useful for smooth transitions between pages and prevents the UI from flickering when navigating.
const page = ref(1);
const { isLoading, isError, data, error, isFetching, isPreviousData } =
useQuery({
queryKey: ["people", page],
queryFn: () => peopleFetcher(page),
keepPreviousData: true,
});
The destructured values from the function call are used mostly in the template. They provide information about the current state of the query, such as whether it is loading, if there is an error, the fetched data, and so on.
The nextPage
function is defined, which increments the page
value by 1
if isPreviousData.value
is false
. This ensures that a new query is triggered only if the previous data has been fetched.
const nextPage = () => {
if (!isPreviousData.value) {
page.value = page.value + 1;
}
};
The prevPage
function is defined, which decrements the page
value by 1
but ensures that the minimum value is 1. This allows navigating to the previous page while preventing going below the first page.
const prevPage = () => {
page.value = Math.max(page.value - 1, 1);
};
The template
for the component primarily used for rendering the data
from the query. The isLoading
and isFetching
variables from the useQuery
function are used to control specific user interface elements to create a more intuitive user experience.
<template>
<template v-if="isLoading && !data">
<h1><ion-loading/></h1>
</template>
<template v-else>
<p>Current Page: {{ page }}</p>
<ion-button @click="prevPage" :disabled="isFetching || page === 1">
{{ isFetching ? "Loading..." : "Prev Page" }}
</ion-button>
<ion-button @click="nextPage" :disabled="isFetching">
{{ isFetching ? "Loading..." : "Next Page" }}
</ion-button>
<ion-list>
<ion-item v-for="item in data" :key="item.login.uuid">
<ion-label>{{ item.email }}</ion-label>
</ion-item>
</ion-list>
</template>
</template>
ExampleInfinite.vue
Component
This component builds upon the previous one and introduces the use of the useInfiniteQuery
hook from the Vue Query library. It enables infinite scrolling by fetching data in a paginated manner.
Several components and functions are imported from the @ionic/vue
and @tanstack/vue-query
packages; the useInfiniteQuery
function from Vue Query is critical to what we are trying to accomplish in this section.
import { IonItem, IonLabel, IonList, IonButton, IonLoading } from "@ionic/vue";
import { useInfiniteQuery } from "@tanstack/vue-query";
The peopleFetcher
function is defined, which takes an object as a parameter destructured with pageParam
defaulting to 1
. This function fetches data from the Random User API based on the provided pageParam value.
The function returns an object with two properties: pageData
contains the fetched data or an empty array, and cursor
represents the value of the next page to fetch. If the pageParam
is 3, indicating the last page, the cursor is set to undefined to stop fetching more pages.
const peopleFetcher = async ({ pageParam = 1 }) => {
const response = await fetch(
`https://randomuser.me/api/?page=${pageParam}&results=10&seed=abc`
);
const data = await response.json();
// fake delay
await new Promise((resolve) => {
setTimeout(() => resolve(true), 1000);
});
// sent the cursor/page value and the results
// set max to 3 pages of data
return {
pageData: data?.results || [],
cursor: pageParam === 3 ? undefined : pageParam + 1,
};
};
The useInfiniteQuery
function is passed properties:
queryKey
property to specify a unique identifier for the query, which in this case is an array with the single element"people"
.queryFn
property is set to thepeopleFetcher
functiongetNextPageParam
property is a function that determines the value to use aspageParam
for the next page fetch. It takes thelastPage
as an argument, which contains thedata
andcursor
value of the last fetched page.
const { data, fetchNextPage, hasNextPage, isFetching, isLoading } =
useInfiniteQuery({
queryKey: ["people"],
queryFn: peopleFetcher,
getNextPageParam: (lastPage) => {
return lastPage.cursor;
},
});
The destructured values from the function call are used mostly in the template.They provide information about the current state of the query, such as whether it is loading, if there is an error, the fetched data, and it includes the fetchNextPage
function which is called to get the next page of data.
The nextPage
function is defined, which calls the fetchNextPage
function from the hook to trigger fetching the next page of data.
const nextPage = () => {
fetchNextPage();
};
The template
for the component primarily used for rendering the data
from the query.
We want to highlight how the data is returned from the query.
You can see from the template code there are nested loops. The outer loop iterates over the data?.pages
array and assigns each element to the variable page
. The inner loop iterates over the page.pageData
array, where page
is the current element from the outer loop. The item
variable is used to represent each element in page.pageData
.
<template>
<h1 v-if="isLoading"><ion-loading /></h1>
<ion-button @click="nextPage" :disabled="isFetching" v-if="hasNextPage">
{{ isFetching ? "Loading..." : "Load More Data" }}
</ion-button>
<ion-list>
<div v-for="(page, index) in data?.pages" :key="index">
<ion-item v-for="item in page.pageData" :key="item.login.uuid">
<ion-label>{{ item.email }}</ion-label>
</ion-item>
</div>
</ion-list>
</template>
Conclusion
This is meant to be a companion to the video and the source code for individuals who like to read code!
💥 Links
Tanstack/Vue Query - tanstack.com/query/latest/docs/vue/overview
Ionic Vue - ionicframework.com/docs/vue/overview
Source Code - github.com/aaronksaunders/vue-query-paging-..
💥 Social Media
Twitter - twitter.com/aaronksaunders
Facebook - facebook.com/ClearlyInnovativeInc
Instagram - instagram.com/aaronksaunders