Chrono Logo

Efficient pagination with SWR

author avatar

Toru Maesaka

Founder and Software Engineer

Pagination is a crucial component in enhancing user experience and easing the load on backend systems. It has been a fundamental web development practice for decades, during which a variety of UI design and backend techniques have emerged. This blog post explores an efficient way to implement pagination in React applications using the useSWRInfinite hook from SWR.

What is SWR?

Officially, SWR is a set of React hooks for data fetching, developed by Vercel and the open source community. By leveraging the conventions and API of SWR, developers can simplify the implementation of data fetching from external sources. However, in practice, SWR offers much more than that. It can be considered a unified framework for data caching and synchronization across the entire application.

Pagination using useSWRInfinite()

SWR provides useSWRInfinite, a React hook specifically designed for pagination. The key advantage of this hook over the standard useSWR hook is its ability to handle multiple page requests by itself. In contrast, achieving the same bahavior with useSWR would require additional complexities, such as abstracting each page request into a separate React component. The interface of useSWRInfinite is similar to useSWR:

import useSWRInfinite from 'swr/infinite'

const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)

The key difference between useSWRInfinite and useSWR is the getKey parameter. In useSWRInfinite, getKey must be a function that takes the current page index number and an array of previously loaded pages. Like useSWR, the goal of the function is to return a string, usually a URL, that is passed to the fetcher function. The returned key is also used by SWR to compute its internal caching key.

getKey: (pageIndex: number, previousPageData: any) => string

Given these APIs, a page request can be invoked by calling the setSize() function.

<button onClick={() => setSize(size + 1)}>Load More</button>

It's also worth noting that because multiple pages are fetched and cached by a single hook, the returned data structure is an array. Each index in this array corresponds to the data retrieved from previous paging requests, representing the data of each page. For more information, refer to the official SWR documentation.

Efficiently using useSWRInfinite()

By default, mutating the useSWRInfinite cache triggers a refetch (revalidation) of all previously fetched pages. This behavior is typically undesirable as it can produce redundant backend I/O operations and consume excessive bandwidth, potentially leading to increased operational costs. However, thanks to a community contribution, you can now selectively revalidate specific page(s).

// Revalidate only the last page.
mutate(data, {
  revalidate: (pageData, [_, page]) => page === size
})

// Revalidate only the first page. Useful for descending-sort feed.
mutate(data, {
  revalidate: (pageData, [_, page]) => page === 0
})

The selective page revalidation feature is available as of SWR v2.2.5. At Chrono, we have incorporated this feature into our change-events timeline and found it to be effective. Most developers should consider taking advantage of this new feature unless there is a business requirement to revalidate everything.

Thoughts on SWR

SWR stands out as an outstanding library for data fetching in React applications, addressing a wide range of use-cases, including various pagination methods. For example, SWR provides useSWRInfinite, a React hook specifically designed for pagination. Despite early concerns about redundant requests potentially being made, this issue has been addressed by the community, further enhancing SWR's appeal as a unified data fetching and caching framework for React applications.

Did you enjoy the content? Follow us on LinkedIn to help us grow and continue delivering blog posts like this one. Your support is appreciated.