Infinite Hook Implementation
Purpose
This subtopic addresses the challenge of efficiently fetching and managing paginated or infinite-loading data within React applications. Unlike the core data fetching hook, which handles a single resource, this implementation extends the capability to support multiple pages or "chunks" of data that can be loaded incrementally. It solves problems such as:
Managing the number of pages loaded dynamically.
Concatenating page data seamlessly for consumer components.
Coordinating revalidation logic across multiple pages.
Supporting parallel or sequential fetching strategies.
Providing a convenient API for updating page size and triggering mutations with cache synchronization.
This functionality is essential for use cases like infinite scrolling lists, paginated tables, or any scenario where data is retrieved in discrete segments rather than in one bulk request.
Functionality
At its core, the infinite hook implementation is a specialized middleware that wraps the existing core SWR hook to extend its behavior for infinite loading scenarios. It introduces:
Page Key Generation: A function (
getKey) generates unique keys for each page based on page index and previous page data. If a falsy key is returned, fetching stops, enabling dynamic page count.Page Size Management: The hook tracks how many pages are currently loaded (
size) and provides asetSizemethod to increase or decrease this number. The current size is stored and observed in cache, enabling updates to trigger re-renders.Data Fetching Workflow: For each page index, the hook fetches data either sequentially or in parallel depending on the
parallelconfiguration flag. It uses the fetcher function (fn) to retrieve data for each page key and caches page data separately.Revalidation Control: The hook supports fine-grained revalidation strategies:
revalidateAllto force all pages to revalidate.revalidateFirstPage to selectively revalidate the first page.
revalidateOnMountto control revalidation behavior on component mount.Custom revalidation per page via a function in
shouldRevalidatePage.
Mutation API: Extends the
mutatemethod to support infinite data arrays, enabling partial or full cache updates with options for revalidation.State Synchronization: Uses React's
useSyncExternalStorefor subscribing to cache changes related to page size and triggers updates accordingly.Cache Helpers and Serialization: Utilizes internal cache helpers and key serialization utilities to manage and isolate cache entries for each page and the overall infinite hook metadata.
Key Code Snippets
Page Size Resolution and Subscription
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
)
This snippet shows how the current page size is derived from cache and how React components can subscribe to cache changes to re-render as the page size updates.
Fetching Multiple Pages
for (let i = 0; i < pageSize; ++i) {
const [pageKey, pageArg] = serialize(
getKey(i, parallel ? null : previousPageData)
)
if (!pageKey) break
// ...fetch or retrieve cached pageData
if (fn && shouldFetchPage) {
await fn(pageArg) // fetch page data
}
data[i] = pageData
if (!parallel) previousPageData = pageData
}
This loop orchestrates fetching each page based on keys until a falsy key is encountered or the designated page size is reached.
Setting New Page Size
const setSize = useCallback(
(arg: number | ((size: number) => number)) => {
// calculate new size
changeSize({ _l: size })
lastPageSizeRef.current = size
// recompose page data from cache and trigger mutate
return mutate(data)
},
[infiniteKey, cache, mutate, resolvePageSize]
)
Allows consumers to change how many pages are loaded, triggering cache updates and revalidation as needed.
Integration
This infinite hook middleware integrates tightly with the core SWR hook by wrapping it and augmenting its behavior to support multiple pages instead of a single resource. It complements the parent topic by:
Building upon the core caching, revalidation, and suspense mechanisms.
Leveraging the same middleware architecture for extensibility and custom behavior.
Sharing utilities like cache helpers and serialization from the internal modules.
Providing a consistent API surface (
data,error,mutate,isValidating) with extended features (size,setSize) specific to infinite loading.
It also coexists with other subtopics such as:
Infinite Loading Types: Which provide the TypeScript definitions that shape the behavior and typing of this hook.
Infinite Key Serialization: Which handles the consistent serialization of page keys, critical for cache isolation and lookup.
By encapsulating the logic for infinite scrolling data fetching, this subtopic enables developers to efficiently build complex paginated UI components without managing page state or revalidation manually.
Diagram
flowchart TD
A[Component Calls useSWRInfinite] --> B[Initialize Infinite Key & Cache]
B --> C[Subscribe to Page Size Changes]
C --> D{For each page index < size}
D -->|Generate Page Key| E[Check cache for page data]
E --> F{Should fetch page?}
F -->|Yes| G[Fetch page data with fetcher]
F -->|No| H[Use cached page data]
G --> I[Update page cache]
H --> I
I --> J[Accumulate page data array]
J --> D
D -->|All pages fetched| K[Return aggregated data array]
K --> L[Expose data, error, isValidating, size, setSize, mutate API]
L --> M[Component renders with data]
This flowchart clarifies the key process of how the infinite hook manages multiple page fetches, cache updates, and exposes an API for consumption.