Tanstack Query / Vue Query - Pagination and Infinite Scroll Example In Vue JS
Tags: vue, vuequery, webdev, tutorial
Published: 2023-07-14 03:04:23

## Overview

Tanstack Query / Vue Query offer 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.

{% youtube 0Njxq9UcL9s %}

This blog post is about an example application that shows how to use [ Tanstack Query / vue-query](https://tanstack.com/query/latest/docs/vue/overview) 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 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](https://vuex.vuejs.org/) and [Pinia](https://pinia.vuejs.org/). I believe that using Tanstack Query, you can simplify some of your state management code in your vue applications
- https://tanstack.com/query/latest/docs/vue/guides/does-this-replace-client-state

>I am using [Ionic Framework](https://ionicframework.com/docs/vue/overview) 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.

```javascript
// main.ts
import { VueQueryPlugin } from '@tanstack/vue-query';

createApp(App)
  .use(IonicVue, {
    mode: 'ios',
  })
  .use(VueQueryPlugin)
  .mount('#app');
```

---

 💥 Sign Up For Our Newsletter​ - https://buff.ly/3lAk2jL

---
## Application Components
The application has two primary components `ExamplePaging.vue` and `ExampleInfinite.vue`. They does basically what their name says which is to show an example of the two typeof queries we discussed for the application
- Paging - the ability to have a predefined page size and navigate forward and backwards though the pages
- Infinite - the ability to just continue to load data to the database will moving 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.
```javascript
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.
```javascript
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 the `peopleFetcher` 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.

```javascript
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.
```javascript
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.
```javascript
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.
```vue
<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.

```javascript
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.

```javascript
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 the `peopleFetcher` function 
- `getNextPageParam` property is a function that determines the value to use as `pageParam` for the next page fetch. It takes the `lastPage` as an argument, which contains the `data` and `cursor` value of the last fetched page.
```javascript
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.
```javascript
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`.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i4ye5xfcrjq2rda2rzdq.png)

```vue
<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](https://youtu.be/0Njxq9UcL9s) and the [source code](https://github.com/aaronksaunders/vue-query-paging-infinite-ionic) for individuals who like to read code!

---

## 💥 Links
- Tanstack/Vue Query - https://tanstack.com/query/latest/docs/vue/overview
- Ionic Vue - https://ionicframework.com/docs/vue/overview
- Source Code - https://github.com/aaronksaunders/vue-query-paging-infinite-ionic

## 💥 Social Media 
- Twitter - https://twitter.com/aaronksaunders
- Facebook - https://www.facebook.com/ClearlyInnovativeInc
- Instagram - https://www.instagram.com/aaronksaunders/