In this post, I’ll teach you how to implement infinite scrolling using TanStack Query, specifically with Infinity Queries. We’ll create a photo feed with Vite and set up infinite scrolling. To start, open your terminal and run the following command to clone a project with basic configurations:
git clone --branch start https://github.com/DAVI-REZENDE/photos-feed.git
cd photos-feed
npm i
All set! Now, let's implement the infinite scroll functionality using the TanStack Query library. Install it with the command below:
npm i @tanstack/react-query
npm i axios
In the App.tsx
file, you'll see that your code looks like this:
First, we’ll replace useEffect
with useInfiniteQuery
, the hook responsible for managing our infinite requests. We must provide it with two properties: queryKey
and queryFn
, as follows:
const {
data,
isLoading,
fetchNextPage,
isFetchingNextPage,
isFetching,
hasNextPage
} = useInfiniteQuery({
queryFn: fetchPhotos,
queryKey: ['photos'],
initialPageParam: 1,
getNextPageParam: (lastPage) => {
return lastPage.nextPage
}
})
Explanation of each parameter:
-
queryFn
: The function responsible for returning our request data; it receives the current page as a parameter. -
queryKey
: Serves as an identifier for your request, also acting as a dependency array. Every time a variable you pass within it changes,useInfiniteQuery
automatically refetches. -
initialPageParam
: The initial default value. -
getNextPageParam
: Receives everything yourqueryFn
function returns and must return the next page number to be requested.
We’ll need to modify the fetchPhotos
function:
async function fetchPhotos({ pageParam }: { pageParam: number }) {
const response = await api.get<ImageData[]>('/photos', {
params: {
page: pageParam,
per_page: 5,
}
})
return {
data: response.data,
nextPage: pageParam + 1
}
}
The useInfiniteQuery
hook returns the data in pages, so our rendering will change slightly:
<main className="h-screen w-screen bg-zinc-950 flex flex-col gap-6 p-6 items-center text-white overflow-auto">
{isLoading ? 'Loading...' : (
<>
{data?.pages.map((group, i) => (
<div className="flex flex-col gap-6" key={i}>
{group.data.map(({ id, urls }) => (
<img className="aspect-square rounded-md h-[550px] object-cover" src={urls.regular} key={id} />
))}
</div>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)}
</main>
Now, each time the user reaches the end of the scroll and clicks the ‘Load More’ button, the data will be automatically appended.
To fetch the next page whenever the user reaches the end of the scroll without needing to click the button, just add the following function:
function handleScroll(event: UIEvent<HTMLElement>) {
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget
if (scrollTop + clientHeight >= scrollHeight) {
fetchNextPage()
}
}
And add the onScroll
event in the div
that wraps your list, calling the function there. Done! Now, every time the user scrolls to the end, new data will automatically load.
In the end, your code should look like this:
import { useInfiniteQuery } from "@tanstack/react-query"
import { UIEvent } from "react"
import { api } from "./lib/api"
type ImageData = {
id: string,
urls: {
regular: string
}
}
export function Home() {
async function fetchPhotos({ pageParam }: { pageParam: number }) {
const response = await api.get<ImageData[]>('/photos', {
params: {
page: pageParam,
per_page: 5,
}
})
return {
data: response.data,
nextPage: pageParam + 1
}
}
const { data, isLoading, fetchNextPage, isFetchingNextPage, isFetching, hasNextPage } = useInfiniteQuery({
queryFn: fetchPhotos,
queryKey: ['photos'],
initialPageParam: 1,
getNextPageParam: (lastPage) => {
return lastPage.nextPage
}
})
function handleScroll(event: UIEvent<HTMLElement>) {
const { scrollTop, clientHeight, scrollHeight } = event.currentTarget
if (scrollTop + clientHeight >= scrollHeight) {
fetchNextPage()
}
};
return (
<main className="h-screen w-screen bg-zinc-950 flex flex-col gap-6 p-6 items-center text-white overflow-auto" onScroll={handleScroll}>
{isLoading ? 'Loading...' : (
<>
{data?.pages.map((group, i) => (
<div className="flex flex-col gap-6" key={i}>
{group.data.map(({ id, urls }) => (
<img className="aspect-square rounded-md h-[550px] object-cover" src={urls.regular} key={id} />
))}
</div>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)}
</main>
)
}
Thank you!
Top comments (0)