index.ts
Overview
The index.ts file implements the infinite loading middleware for the SWR (stale-while-revalidate) React hook library. Its purpose is to enable efficient, declarative fetching and caching of paginated or infinite lists of data within React applications via the useSWRInfinite hook.
This middleware extends the core useSWR hook by managing multiple page keys, page sizes, and revalidation strategies explicitly designed for infinite scrolling or "load more" UI patterns. It orchestrates individual page data fetching, caching, and revalidation while exposing a unified API to interact with the entire paginated data set as an array.
Key features include:
Dynamic page key generation based on page index and previous page data.
Centralized page size state stored in cache to persist across mounts.
Parallel or sequential fetching of pages.
Fine-grained revalidation control at page and global levels.
Extended mutation API for partial or full cache updates.
Integration with global cache and preload request mechanisms.
Usage of React’s
useSyncExternalStorefor efficient cache subscription.
Detailed Explanation
Main Export: useSWRInfinite
useSWRInfinite is the custom React hook built by composing the core useSWR hook with the infinite loading middleware. It provides an API tailored for infinite or paginated data fetching scenarios.
const useSWRInfinite = withMiddleware(useSWR, infinite) as SWRInfiniteHook
export default useSWRInfinite
Infinite Middleware Function: infinite
This is the core middleware function that adds infinite loading logic on top of a base SWR hook (useSWRNext).
export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>
(
getKey: SWRInfiniteKeyLoader,
fn: BareFetcher<Data> | null,
config: Omit<typeof SWRConfig.defaultValue, 'fetcher'> &
Omit<SWRInfiniteConfiguration<Data, Error>, 'fetcher'>
) => { ... }
) as unknown as Middleware
Parameters
useSWRNext: SWRHook
The base SWR hook to wrap, typically the coreuseSWRhook.Returns a hook function with these arguments:
getKey: SWRInfiniteKeyLoader
A function(pageIndex: number, previousPageData: Data | null) => Key | nullthat returns the key for each page. If it returns a falsy key, fetching stops at that page.fn: BareFetcher<Data> | null
Optional fetcher function to retrieve the page data.config: object
Configuration options for infinite loading, excluding thefetcherproperty. Includes flags likeinitialSize,revalidateAll,persistSize,parallel, and callbacks likecompareandshouldRevalidatePage.
Returned Object (Infinite Hook API)
The hook returns an object with the following properties and methods:
Property/Method | Type | Description |
|---|---|---|
|
| Current number of pages loaded. |
| `(size: number \ | (size: number) => number) => Promise<Data[]> \ |
|
| Extended mutate function to mutate the cached pages array or trigger revalidation. |
| `Data[] \ | undefined` |
| `Error \ | undefined` |
|
| Indicates whether any page is currently validating (fetching). |
|
| Indicates whether the initial loading state is ongoing. |
Important Internal Variables and Hooks
didMountRef: React.RefObject<boolean>
Tracks if the hook has mounted to control certain revalidation logic.infiniteKey: string | undefined
Serialized key for the first page prefixed withINFINITE_PREFIX. Used as the global anchor key for storing infinite hook metadata (e.g., page size).Cache helpers:
get, set, subscribeCache— helper functions to interact with the SWR cache for the infinite key metadata.Similar helpers for individual page keys are used inside the fetcher.
useSyncExternalStore
Subscribes React to changes in the infinite key cache metadata (specifically page size), enabling components to rerender when the size changes.resolvePageSize
Returns the current page size stored in cache or falls back toinitialSize.lastPageSizeRef
Stores the last page size to supportpersistSizebehavior when keys change.
Data Fetching Logic (Fetcher in useSWRNext)
The middleware provides a custom fetcher function to useSWRNext that:
Reads the current page size.
Iterates from
0topageSize - 1:Calls
getKey(i, previousPageData)to get the page key and arguments.If the key is falsy, fetching stops early.
Reads cached data for that page.
Determines if the page should be revalidated/fetched based on config flags and cache state.
If fetching is needed, calls the fetcher function
fnor resolves from preload cache if available.Stores the fetched page data in cache.
Supports fetching pages in parallel or sequentially based on the
parallelconfig flag.Returns an array of page data for all loaded pages.
mutate Method
The mutate method extends SWR's mutate function:
Accepts new data (or a callback) for the entire pages array.
Accepts options to control revalidation behavior (
revalidateflag).Sets internal flags in cache to indicate whether to revalidate all pages or selectively.
Delegates mutation to the underlying SWR hook.
setSize Method
Allows updating the number of pages to load.
Accepts a number or a function
(size: number) => number.Updates the
_l(length) property in the infinite key cache metadata.Updates
lastPageSizeReffor persistence.Rebuilds the pages data array from individual page caches.
Calls
mutatewith the new data array to trigger revalidation and update.
Usage Example
import useSWRInfinite from 'path-to/useSWRInfinite'
function MyComponent() {
const { data, error, size, setSize, mutate, isValidating } = useSWRInfinite(
(index, previousPageData) => {
// Stop fetching if no more data
if (previousPageData && !previousPageData.length) return null
return `/api/items?page=${index + 1}`
},
fetcher,
{
initialSize: 3,
revalidateAll: false,
parallel: true
}
)
if (error) return <div>Error loading data</div>
if (!data) return <div>Loading...</div>
// Flatten loaded pages
const items = data.flat()
return (
<>
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<button onClick={() => setSize(size + 1)}>Load More</button>
</>
)
}
Important Implementation Details
Cache Structure:
Uses a special infinite key (prefixed) to store metadata such as current page size (_l), revalidation flags (_i,_r), and page cache keys.Key Serialization:
The first page key is serialized and prefixed to generate the infinite key. Each page key is also serialized for cache isolation.Revalidation Control:
Flags stored in cache (_i,_r) coordinate which pages need to be revalidated when mutation or re-fetching occurs.Preload Cache Integration:
Supports preloading pages viaPRELOADcache, allowing data to be resolved from preloaded promises instead of fetching again.React Sync Subscription:
UsesuseSyncExternalStoreto listen for cache changes on page size, ensuring components update only when necessary.Parallel Fetching:
Supports fetching multiple pages in parallel for performance or sequentially when previous page data is needed for the next page key.Persist Page Size:
Optionally persists the page size when the key changes, preventing unwanted resets.Error Handling:
If thegetKeyfunction throws or returns an invalid key, fetching stops gracefully.
Interactions with Other Parts of the System
Core
useSWRHook:
The infinite middleware composes the coreuseSWRhook, extending it to support multiple pages instead of a single resource.Global Cache (
defaultCache):
Stores infinite hook metadata and individual page data, enabling shared cache state across components.Serialization Utilities (
serialize,getFirstPageKey):
Used for converting keys into stable strings for cache indexing.Global State (
SWRGlobalState):
Used for managing preload cache and other global SWR internals.Internal Cache Helpers (
createCacheHelper):
Abstract cache get/set/subscribe operations scoped to specific keys for isolation.Middleware Composition (
withMiddleware):
Wraps the core SWR hook with the infinite middleware, allowing extension via composable layers.
Mermaid Class Diagram
classDiagram
class InfiniteMiddleware {
- didMountRef: Ref<boolean>
- infiniteKey: string | undefined
- lastPageSizeRef: Ref<number>
- get: Function
- set: Function
- subscribeCache: Function
- resolvePageSize(): number
+ useSWRNext(getKey, fetcher, config): SWRResponse<Data[]>
+ mutate(data?, opts?): Promise<Data[]>
+ setSize(size | (size) => size): Promise<Data[]>
+ data: Data[]
+ error: Error | undefined
+ isValidating: boolean
+ isLoading: boolean
+ size: number
}
class useSWRInfinite {
+ (getKey, fetcher, config): {
size: number
setSize: Function
mutate: Function
data: Data[]
error: Error | undefined
isValidating: boolean
isLoading: boolean
}
}
useSWRInfinite --|> InfiniteMiddleware
Summary
The index.ts file implements the infinite loading middleware for SWR, enabling React developers to easily fetch, cache, and manage paginated or infinite scroll data. It extends the core SWR hook into a powerful useSWRInfinite hook with advanced features like dynamic page keys, configurable revalidation, parallel fetching, and cache-backed page size state.
This modular and extensible design integrates deeply with SWR’s global cache and middleware architecture, providing robust infinite loading capabilities while maintaining a simple, declarative API surface.