Infinite Loading Support

Overview

The Infinite Loading Support module extends the core data fetching hook to enable fetching paginated or infinite lists of data in a declarative and efficient way. It solves the challenge of managing multiple pages of data that must be loaded progressively, concatenated, and cached, while maintaining synchronization with the global cache and minimizing unnecessary re-fetches.

By providing a specialized hook, useSWRInfinite, this module abstracts the complexity of managing page keys, page sizes, and revalidation logic across dynamic paginated data sets. It empowers developers to implement infinite scrolls, "load more" buttons, and similar UI patterns seamlessly integrated with SWR’s caching and revalidation mechanisms.


Core Concepts

1. Page-Based Data Fetching with Dynamic Keys

At the heart of infinite loading is the concept of fetching data page by page. Each page corresponds to a unique key generated by a key loader function, which takes the page index and optionally the previous page’s data to compute the key for the current page.

This key loader function enables conditional fetching (e.g., stop fetching when no more data) and supports complex pagination schemes (cursor-based, offset-based, etc.).

2. Centralized Page Size Management

The module manages the number of pages currently loaded via a page size state that can be read and updated through the size and setSize API. This state is cached globally to persist across component remounts and share across hooks with the same key.

3. Seamless Integration with Core SWR

The infinite loading hook leverages the core useSWR hook but composes it to handle multiple pages’ keys and data collectively. It coordinates revalidation across pages, supports parallel or sequential fetching strategies, and exposes a unified API for the array of loaded page data.


How It Works

useSWRInfinite Hook

This hook is implemented by composing the core useSWR hook with an infinite loading middleware (the infinite function). The middleware adds the infinite loading logic on top of the standard SWR behaviors.

const useSWRInfinite = withMiddleware(useSWR, infinite) as SWRInfiniteHook

Key Loader and Key Serialization

const infiniteKey = INFINITE_PREFIX + getFirstPageKey(getKey)

Page Size and Cache State

Data Fetching Workflow

When the hook is mounted or revalidated:

  1. It determines the number of pages to load (pageSize).

  2. For each page index up to pageSize, it calls the key loader to get the page key and arguments.

  3. For each valid page key:

    • It attempts to read cached data for that page.

    • It decides whether to fetch fresh data for that page based on:

      • Configuration flags (revalidateAll, revalidateFirstPage, revalidateOnMount).

      • Cache state and whether the page data changed.

      • A custom shouldRevalidatePage function if provided.

  4. It fetches data pages either sequentially or in parallel depending on the parallel config flag.

  5. It stores each page’s data individually in the cache.

  6. Finally, it returns an array of page data.

Mutate and Revalidation

React State and Subscriptions


Interactions with Other System Parts


Design Patterns and Unique Approaches


Key Code Snippets

Infinite Key Creation with Serialization

let infiniteKey: string | undefined
try {
  infiniteKey = getFirstPageKey(getKey)
  if (infiniteKey) infiniteKey = INFINITE_PREFIX + infiniteKey
} catch (err) {
  // Not ready yet.
}

Page Size Subscription Using useSyncExternalStore

const getSnapshot = useCallback(() => {
  const size = isUndefined(get()._l) ? initialSize : get()._l
  return size
}, [cache, infiniteKey, initialSize])

useSyncExternalStore(
  useCallback(
    (callback: () => void) => {
      if (infiniteKey)
        return subscribeCache(infiniteKey, () => {
          callback()
        })
      return () => {}
    },
    [cache, infiniteKey]
  ),
  getSnapshot,
  getSnapshot
)

Fetching Multiple Pages Sequentially or in Parallel

for (let i = 0; i < pageSize; ++i) {
  const [pageKey, pageArg] = serialize(
    getKey(i, parallel ? null : previousPageData)
  )
  if (!pageKey) break

  const revalidate = async () => {
    if (!hasPreloadedRequest) {
      pageData = await fn(pageArg)
    } else {
      pageData = await PRELOAD[pageKey]
      delete PRELOAD[pageKey]
    }
    setSWRCache({ data: pageData, _k: pageArg })
    data[i] = pageData
  }

  if (parallel) {
    revalidators.push(revalidate)
  } else {
    await revalidate()
  }

  if (!parallel) previousPageData = pageData
}

if (parallel) await Promise.all(revalidators.map(r => r()))

setSize API to Change Number of Loaded Pages

const setSize = useCallback(
  (arg: number | ((size: number) => number)) => {
    if (!infiniteKey) return EMPTY_PROMISE
    let size = isFunction(arg) ? arg(resolvePageSize()) : arg
    if (typeof size !== 'number') return EMPTY_PROMISE

    changeSize({ _l: size })
    lastPageSizeRef.current = size

    // Construct new page data array from cache or trigger mutate
    // ...
    return mutate(data)
  },
  [infiniteKey, cache, mutate, resolvePageSize]
)

Mermaid Sequence Diagram: Infinite Loading Data Fetching Workflow

sequenceDiagram
  participant Comp as React Component
  participant Hook as useSWRInfinite Hook
  participant Cache as Global Cache
  participant Fetcher as Data Fetcher

  Comp->>Hook: Mount with getKey and config
  Hook->>Cache: Read infiniteKey and pageSize metadata
  loop For each page index < pageSize
    Hook->>Hook: Generate pageKey with getKey(index, prevPageData)
    Hook->>Cache: Get cached pageData for pageKey
    alt Should fetch page
      Hook->>Fetcher: Fetch page data for pageKey
      Fetcher-->>Hook: Return page data
      Hook->>Cache: Cache page data
    else Use cached pageData
    end
    Hook->>Hook: Update pageData array
  end
  Hook-->>Comp: Return array of pageData

Summary

The Infinite Loading Support module provides a powerful extension to the core data fetching hook, enabling efficient and flexible loading of paginated data sets. Its design leverages middleware composition, cache-backed state, dynamic key generation, and configurable fetching strategies to solve the complexities of infinite scrolling and pagination within the SWR ecosystem.