Infinite scroll component for React that supports both window and element-based scrolling with customizable thresholds and reverse mode.
npx @tessl/cli install tessl/npm-react-infinite-scroller@1.2.0React Infinite Scroller is a lightweight React component that provides infinite scrolling functionality for both window and element-based scroll detection. It supports customizable thresholds, reverse scrolling for chat-like interfaces, and passive event listeners for optimal performance.
npm install react-infinite-scrollerimport InfiniteScroll from 'react-infinite-scroller';For CommonJS:
const InfiniteScroll = require('react-infinite-scroller');import React, { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
function MyComponent() {
const [items, setItems] = useState([]);
const [hasMore, setHasMore] = useState(true);
const loadMore = async (page) => {
try {
// Note: page starts from pageStart + 1 (so if pageStart=0, first call gets page=1)
const response = await fetch(`/api/items?page=${page}`);
const newItems = await response.json();
if (newItems.length === 0) {
setHasMore(false);
} else {
setItems(prevItems => [...prevItems, ...newItems]);
}
} catch (error) {
console.error('Failed to load items:', error);
}
};
return (
<InfiniteScroll
pageStart={0}
loadMore={loadMore}
hasMore={hasMore}
loader={<div className="loader" key={0}>Loading...</div>}
>
<div>
{items.map((item, index) => (
<div key={index}>{item.name}</div>
))}
</div>
</InfiniteScroll>
);
}Core React component that handles infinite scroll functionality with comprehensive configuration options.
/**
* React component for infinite scrolling functionality
* @component
*/
function InfiniteScroll(props: InfiniteScrollProps): React.ReactElement;
interface InfiniteScrollProps {
/** Content to be rendered inside the scroll container (required) */
children: React.ReactNode;
/** Callback function triggered when more content needs to be loaded (required)
* @param page - Page number starting from pageStart + 1 */
loadMore: (page: number) => void;
/** HTML element type to render as container */
element?: string | React.ComponentType;
/** Whether more items are available to load
* When false, all scroll event listeners are automatically removed */
hasMore?: boolean;
/** Whether to trigger loadMore on component mount */
initialLoad?: boolean;
/** Whether to load content when scrolling to top (chat-like behavior) */
isReverse?: boolean;
/** React element to display while loading */
loader?: React.ReactNode;
/** Starting page number for loadMore callback */
pageStart?: number;
/** Ref callback to access the scroll container element
* Note: Called after internal ref processing for scroll detection */
ref?: (node: HTMLElement | null) => void;
/** Function to override default scroll parent detection */
getScrollParent?: () => HTMLElement;
/** Distance in pixels from scroll end to trigger loadMore */
threshold?: number;
/** Event listener capture option */
useCapture?: boolean;
/** Whether to use window scroll events or parent element events */
useWindow?: boolean;
}Default Props:
element: 'div'hasMore: falseinitialLoad: truepageStart: 0ref: nullthreshold: 250useWindow: trueisReverse: falseuseCapture: falseloader: nullgetScrollParent: nullDefault behavior using window scroll events for infinite scrolling.
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true}
loader={<div className="loader" key={0}>Loading...</div>}
>
{items}
</InfiniteScroll>Infinite scrolling within a specific scrollable container element.
<div style={{height: '400px', overflow: 'auto'}}>
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true}
loader={<div className="loader" key={0}>Loading...</div>}
useWindow={false}
>
{items}
</InfiniteScroll>
</div>Using a custom parent element for scroll calculations with getScrollParent.
function MyComponent() {
const scrollParentRef = useRef(null);
return (
<div
style={{height: '400px', overflow: 'auto'}}
ref={scrollParentRef}
>
<div>
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true}
loader={<div className="loader" key={0}>Loading...</div>}
useWindow={false}
getScrollParent={() => scrollParentRef.current}
>
{items}
</InfiniteScroll>
</div>
</div>
);
}Loading content when scrolling to the top, useful for chat interfaces.
<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true}
loader={<div className="loader" key={0}>Loading...</div>}
isReverse={true}
>
{messages}
</InfiniteScroll>Sets a default loader component for all InfiniteScroll instances.
/**
* Set a default loader for all InfiniteScroll components
* @param loader - React element to use as default loader
*/
setDefaultLoader(loader: React.ReactNode): void;Usage:
const infiniteScrollRef = useRef(null);
// Set default loader on component instance
useEffect(() => {
if (infiniteScrollRef.current) {
infiniteScrollRef.current.setDefaultLoader(
<div className="default-loader">Loading more items...</div>
);
}
}, []);
return (
<InfiniteScroll
ref={infiniteScrollRef}
pageStart={0}
loadMore={loadFunc}
hasMore={true}
>
{items}
</InfiniteScroll>
);The component automatically handles:
hasMore becomes falseloadMore callback receives page numbers starting from pageStart + 1function InfiniteList() {
const [items, setItems] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const loadMore = async (page) => {
if (isLoading) return; // Prevent overlapping requests
setIsLoading(true);
try {
const newItems = await fetchItems(page);
if (newItems.length === 0) {
setHasMore(false);
} else {
setItems(prev => [...prev, ...newItems]);
}
} finally {
setIsLoading(false);
}
};
return (
<InfiniteScroll
pageStart={0}
loadMore={loadMore}
hasMore={hasMore && !isLoading}
loader={<div>Loading...</div>}
>
<div>
{items.map((item, index) => (
<div key={item.id || index}>{item.content}</div>
))}
</div>
</InfiniteScroll>
);
}<InfiniteScroll
pageStart={0}
loadMore={loadFunc}
hasMore={true}
threshold={100} // Trigger load when 100px from bottom
loader={<div>Loading...</div>}
>
{items}
</InfiniteScroll>const loadMore = async (page) => {
try {
const newItems = await fetchItems(page);
setItems(prev => [...prev, ...newItems]);
setHasMore(newItems.length > 0);
} catch (error) {
console.error('Failed to load items:', error);
setHasMore(false); // Stop further loading attempts
}
};