DEV Community

Cover image for Infinite Scrolling of TanStack Query 🌸
Abdul Ahad Abeer
Abdul Ahad Abeer

Posted on • Originally published at abeer.hashnode.dev

Infinite Scrolling of TanStack Query 🌸

This article is a continuation of the previous article. So, check that out before you continue.

For infinite scrolling, create an axios function for api calling:

export const getProducts = async ({ pageParam }: { pageParam: number }) => {
  return (
    await axiosInstance.get<Product[]>(
      `products?_page=${pageParam + 1}&_limit=3`
    )
  ).data
}
Enter fullscreen mode Exit fullscreen mode

Now, we need to define an infinite-scrolling specific query:

export function useProducts() {
  return useInfiniteQuery({
    queryKey: ["products"],
    queryFn: getProducts,
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages, lastPageParam) => {
      if (lastPage.length === 0) {
        return undefined
      }
      return lastPageParam + 1
    },
    getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
      if (firstPageParam <= 1) {
        return undefined
      }
      return firstPageParam - 1
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Here, we can replace unused parameters with underscores like the following:

getNextPageParam: (lastPage, _, lastPageParam) => {

// -------------------------------------------------

getPreviousPageParam: (_, __, firstPageParam) => {
Enter fullscreen mode Exit fullscreen mode

The properties of useInfiniteQuery are pretty much self-explanatory. Now, implement the infinite query mechanisms in the react component: (This one is long. There will be some explanation after the code)

import { FC, Fragment, useState } from "react"
import { useProduct, useProducts } from "../services/queries"

interface ComponentProps {}

const Products: FC<ComponentProps> = () => {
  const [selectedProductId, setSelectedProductId] = useState<number | null>(
    null
  )

  const productsQuery = useProducts()
  const productQuery = useProduct(selectedProductId)

  return (
    <>
      {/* page data rendered here */}
      {productsQuery.data?.pages.map((page, index) => (
        <Fragment key={index}>
          {page.map((product) => (
            <Fragment key={product.id}>
              {/* click button to see in detail */}
              <button onClick={() => setSelectedProductId(product.id)}>
                {product.name}
              </button>
              <br />
            </Fragment>
          ))}
        </Fragment>
      ))}
      <br />

      {/* button to load to the next page */}
      <div>
        <button
          onClick={() => productsQuery.fetchNextPage()}
          disabled={
            !productsQuery.hasNextPage || productsQuery.isFetchingNextPage
          }
        >
          {productsQuery.isFetchingNextPage
            ? "Loading more..."
            : productsQuery.hasNextPage
            ? "Load More"
            : "Nothing more to load"}
        </button>
      </div>

      {/* selected product in detail */}
      <div>Selected product:</div>
      {JSON.stringify(productQuery.data)}
    </>
  )
}

export default Products
Enter fullscreen mode Exit fullscreen mode

useProducts is used for making pagination api call. So, we get data of the first page and render them initially.

Then, another mechanism is added to select a product and see data in detail. In the rendered page data, there is a button to select, and its data is shown in the bottom. The selected data comes from useProduct api calling. The following is how it works:

// api.ts
export const getProduct = async (id: number) => {
  return (await axiosInstance.get<Product>(`products/${id}`)).data
}

// queries.ts
export function useProduct(id: number | null) {
  const queryClient = useQueryClient()

  return useQuery({
    queryKey: ["product", { id }],
    queryFn: () => getProduct(id!),
    enabled: !!id,
    placeholderData: () => {
      const cachedProducts = (
        queryClient.getQueryData(["products"]) as {
          pages: Product[] | undefined
        }
      )?.pages?.flat(2)

      if (cachedProducts) {
        return cachedProducts.find((item) => item.id === id)
      }
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Here getProduct is for making the api call.

We call the api through useProduct. this api calling works only when there is an id passed through, as enabled is set to id’s existence.

placeholderData sets cached data when data is being fetched and fills the place with cached data.

Top comments (0)