React Window Infinite Loader
react-window-infinite-loader provides utilities for implementing infinite scrolling lists with `react-window`. It is inspired by `react-virtualized`'s `InfiniteLoader` but is specifically designed to work as a lightweight companion for `react-window`'s fixed and variable size lists. The current stable version is 2.0.1, released as of this documentation. The library's release cadence is tied to its maintainer's work on `react-window` and `react-virtualized`, often seeing updates for React compatibility or minor feature enhancements. Key differentiators include its tight integration with `react-window`, offering both a `useInfiniteLoader` hook for functional components and a `InfiniteLoader` component for class-based or render-prop patterns, making it adaptable to various component architectures. It helps manage the loaded state of rows and triggers data loading as users scroll near the end of the list.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'then')
cause `loadMoreRows` did not return a Promise.fixMake sure your `loadMoreRows` function returns a Promise that resolves when data fetching is complete. If using `async/await`, ensure the `async` keyword is present. If using a standard Promise, ensure it is explicitly returned. -
React Hook 'useInfiniteLoader' is called in a function that is neither a React function component nor a custom React Hook function.
cause `useInfiniteLoader` is called outside of a functional React component or another custom hook.fixEnsure `useInfiniteLoader` is only called at the top level of a functional React component or within another custom hook that itself adheres to React Hook rules.
Warnings
- breaking The `InfiniteLoader` component underwent significant changes in version 2. The `onItemsRendered` parameter in its child function was renamed to `onRowsRendered`, and the `listRef` parameter was entirely removed. Existing code using the `InfiniteLoader` component from v1 will break if not updated to reflect these parameter changes.
- gotcha It's crucial that the `loadMoreRows` prop passed to `useInfiniteLoader` or `InfiniteLoader` returns a Promise. If it does not return a Promise that resolves when data is loaded, the component will not correctly track the loading state, potentially leading to repeated fetches or a broken infinite scroll experience.
- gotcha Incorrectly managing the `isRowLoaded` state can lead to either rows perpetually showing as loading or `loadMoreRows` being called too frequently or not at all. The `isRowLoaded` function must accurately reflect whether the data for a given `index` is available.
Install
-
npm install react-window-infinite-loader -
yarn add react-window-infinite-loader -
pnpm add react-window-infinite-loader
Imports
- useInfiniteLoader
import useInfiniteLoader from 'react-window-infinite-loader'
import { useInfiniteLoader } from 'react-window-infinite-loader' - InfiniteLoader
const InfiniteLoader = require('react-window-infinite-loader')import { InfiniteLoader } from 'react-window-infinite-loader' - InfiniteLoaderProps
import type { InfiniteLoaderProps } from 'react-window-infinite-loader'
Quickstart
import { useInfiniteLoader } from 'react-window-infinite-loader';
import { FixedSizeList } from 'react-window';
import React, { useState, useCallback } from 'react';
const Row = ({ index, style, isLoaded }) => (
<div style={style}>
{isLoaded ? `Row ${index}` : `Loading row ${index}...`}
</div>
);
const ExampleList = ({ itemCount, isRowLoaded, loadMoreRows }) => {
const [items, setItems] = useState(Array(itemCount).fill(false));
const infiniteLoaderProps = {
isRowLoaded,
loadMoreRows,
itemCount,
threshold: 5,
minimumBatchSize: 10
};
const onRowsRendered = useInfiniteLoader(infiniteLoaderProps);
return (
<FixedSizeList
height={300}
itemCount={itemCount}
itemSize={50}
width={500}
onRowsRendered={onRowsRendered}
>
{({ index, style }) => (
<Row index={index} style={style} isLoaded={isRowLoaded({ index })} />
)}
</FixedSizeList>
);
};
function App() {
const ROW_COUNT = 1000;
const isItemLoaded = ({ index }) => index < loadedItems.length;
const [loadedItems, setLoadedItems] = useState(Array(50).fill(true));
const loadMore = useCallback(async (startIndex, stopIndex) => {
console.log(`Loading rows from ${startIndex} to ${stopIndex}`);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
setLoadedItems(prev => {
const newItems = [...prev];
for (let i = startIndex; i <= stopIndex; i++) {
if (!newItems[i]) {
newItems[i] = true;
}
}
return newItems;
});
}, []);
return (
<ExampleList
itemCount={ROW_COUNT}
isRowLoaded={isItemLoaded}
loadMoreRows={loadMore}
/>
);
}
export default App;