reactjs-scroll-pagination
v8.0.0
Published
A React component for infinite scroll pagination with smart prefetching that works seamlessly with React and Next.js applications
Maintainers
Readme
A powerful, lightweight React component for infinite scroll pagination that works seamlessly with React and Next.js applications. Features include smart prefetching, error handling, retry mechanism, custom loaders, scroll direction detection, and much more!
✨ Features
- 🚀 Next.js SSR Compatible - Works perfectly with server-side rendering
- 🎯 TypeScript Support - Fully typed for better developer experience
- ⚡ Performance Optimized - Uses IntersectionObserver API for efficient scroll detection
- � Smart Prefetching - Prefetch next page before reaching bottom with configurable offset
- 💤 Idle Prefetching - Optionally prefetch when the browser is idle
- 📈 Adaptive Prefetch - Increase prefetch distance for fast scrolling
- �🔄 Error Handling - Built-in error handling with retry functionality
- 🎨 Customizable - Highly customizable loaders, messages, and styles
- 📱 Mobile Friendly - Works great on all devices
- 🔍 Scroll Direction Detection - Load more based on scroll direction
- ⏱️ Debouncing - Prevent multiple rapid API calls
- 🚦 Throttling - Limit load calls per time window
- 🛑 Visibility Pause - Pause loading when the tab is hidden
- 🔁 Reverse Mode - Load content at the top (useful for chat apps)
- 🎭 Custom Loaders - Use your own loading components
- 💪 Lightweight - No heavy dependencies
Install
npm install reactjs-scroll-paginationor
yarn add reactjs-scroll-paginationBasic Usage
import React, { useState } from 'react';
import ScrollPagination from 'reactjs-scroll-pagination';
const MyComponent = () => {
const [data, setData] = useState([1, 2, 3, 4, 5]);
const [hasMore, setHasMore] = useState(true);
const loadMore = () => {
// Simulate API call
setTimeout(() => {
const newData = [...data, ...Array(5).fill(0).map((_, i) => data.length + i + 1)];
setData(newData);
if (newData.length >= 50) setHasMore(false);
}, 1000);
};
return (
<div>
<ScrollPagination
loading={<div>Loading more...</div>}
loadMore={loadMore}
hasMore={hasMore}
>
{data.map((item, index) => (
<div key={index}>Item {item}</div>
))}
</ScrollPagination>
</div>
);
};
export default MyComponent;Advanced Usage with Next.js
'use client'; // For Next.js 13+ App Router
import React, { useState } from 'react';
import ScrollPagination from 'reactjs-scroll-pagination';
const ProductList = () => {
const [products, setProducts] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const loadMore = async () => {
try {
const response = await fetch(`/api/products?page=${page}`);
const newProducts = await response.json();
setProducts([...products, ...newProducts]);
setPage(page + 1);
if (newProducts.length < 10) {
setHasMore(false);
}
} catch (error) {
console.error('Failed to load products:', error);
throw error; // Will trigger error handling
}
};
return (
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
loader={<div className="spinner">Loading...</div>}
endMessage={<p>No more products to load!</p>}
threshold={0.5}
rootMargin="200px"
debounceMs={300}
onError={(error) => console.error('Error loading data:', error)}
retryOnError={true}
>
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
</ScrollPagination>
);
};API Reference
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| children | ReactNode | Yes | - | The content to be rendered |
| loadMore | () => void | Promise | Yes | - | Function to load more data |
| hasMore | boolean | Yes | - | Whether there is more data to load |
| loading | ReactNode | No | "Loading more..." | Loading message/component |
| loader | ReactNode | (() => ReactNode) | No | null | Custom loader component |
| endMessage | ReactNode | (() => ReactNode) | No | null | Message shown when no more data |
| threshold | number | No | 0.1 | Intersection observer threshold (0-1) |
| rootMargin | string | No | "10px" | Margin around the root element |
| reverse | boolean | No | false | Load content at the top instead of bottom |
| scrollDirection | 'up' | 'down' | 'both' | No | 'down' | Trigger loading based on scroll direction |
| onError | (error: Error) => void | No | null | Error callback function |
| onRetry | (error: Error, attempt: number, delayMs: number, isPrefetch: boolean) => void | No | null | Called before each retry attempt |
| retryOnError | boolean | No | false | Show retry button on error |
| retryAttempts | number | No | 0 | Number of automatic retry attempts |
| retryDelayMs | number | No | 500 | Base retry delay in milliseconds |
| retryBackoffFactor | number | No | 2 | Backoff multiplier for retries |
| retryMaxDelayMs | number | No | 8000 | Maximum retry delay in milliseconds |
| className | string | No | "" | CSS class for the wrapper div |
| loaderClassName | string | No | "" | CSS class for the loader div |
| initialLoad | boolean | No | false | Trigger loadMore on component mount |
| debounceMs | number | No | 0 | Debounce time in milliseconds |
| throttleMs | number | No | 0 | Throttle window in milliseconds |
| pauseWhenHidden | boolean | No | true | Pause loading when the tab is hidden |
| enablePrefetch | boolean | No | false | Enable smart prefetching of next page |
| prefetchOffset | number | No | 500 | Distance in pixels before triggering prefetch |
| prefetchStrategy | 'visibility' | 'idle' | 'visibility-idle' | No | 'visibility' | When prefetch should trigger |
| adaptivePrefetch | boolean | No | false | Adapt prefetch distance based on scroll speed |
| prefetchMinOffset | number | No | 200 | Minimum adaptive prefetch offset |
| prefetchMaxOffset | number | No | 2000 | Maximum adaptive prefetch offset |
| prefetchSpeedFactor | number | No | 500 | Multiplier for scroll speed to offset |
🔮 Smart Prefetching
The smart prefetching feature allows you to load the next page of data before the user reaches the bottom of the current content, providing a seamless scrolling experience with no loading delays.
How It Works
When enablePrefetch is enabled, the component uses a second IntersectionObserver that triggers at a configurable distance (prefetchOffset) before the actual loader element. This means:
- User scrolls down through content
- When they're
prefetchOffsetpixels away from the loader, the next page starts loading - By the time they reach the bottom, the new content is already loaded
- Creates a smooth, infinite scroll experience with no waiting
Configuration
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
enablePrefetch={true} // Enable the feature
prefetchOffset={800} // Trigger 800px before reaching loader
>
{children}
</ScrollPagination>Best Practices
- Fast connections: Use smaller
prefetchOffsetvalues (300-500px) - Slow connections: Use larger
prefetchOffsetvalues (1000-1500px) - Mobile devices: Consider using 600-800px for better mobile experience
- Combine with debouncing: Add
debounceMs={200}to prevent rapid API calls - Monitor performance: Use
onErrorto track failed prefetch requests
Example with Optimal Settings
const OptimizedInfiniteScroll = () => {
const [data, setData] = useState([]);
const [hasMore, setHasMore] = useState(true);
const loadMore = async () => {
const newData = await fetchDataFromAPI();
setData([...data, ...newData]);
if (newData.length === 0) setHasMore(false);
};
return (
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
enablePrefetch={true}
prefetchOffset={700}
debounceMs={200}
threshold={0.5}
onError={(err) => console.error('Prefetch failed:', err)}
>
{data.map(item => <Card key={item.id} {...item} />)}
</ScrollPagination>
);
};Examples
With Custom Loader
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
loader={
<div className="flex justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
</div>
}
>
{children}
</ScrollPagination>With End Message
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
endMessage={
<div className="text-center py-4">
<p>🎉 You've reached the end!</p>
</div>
}
>
{children}
</ScrollPagination>With Error Handling and Retry
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
onError={(error) => {
console.error('Load failed:', error);
// Send to error tracking service
}}
retryOnError={true}
>
{children}
</ScrollPagination>Reverse Mode (Chat Application)
<ScrollPagination
loadMore={loadMoreMessages}
hasMore={hasMore}
reverse={true}
scrollDirection="up"
>
{messages.map(msg => <Message key={msg.id} {...msg} />)}
</ScrollPagination>With Debouncing
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
debounceMs={500}
threshold={0.5}
rootMargin="100px"
>
{children}
</ScrollPagination>With Initial Load
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
initialLoad={true}
>
{children}
</ScrollPagination>With Smart Prefetching
// Prefetch next page 500px before reaching the loader
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
enablePrefetch={true}
prefetchOffset={500}
>
{children}
</ScrollPagination>With Advanced Prefetching Configuration
// Prefetch earlier for faster perceived performance
<ScrollPagination
loadMore={loadMore}
hasMore={hasMore}
enablePrefetch={true}
prefetchOffset={1000} // Trigger 1000px before the loader
debounceMs={200} // Debounce to prevent rapid calls
threshold={0.3}
rootMargin="100px"
>
{children}
</ScrollPagination>TypeScript Support
This package is written in TypeScript and provides full type definitions:
import ScrollPagination, { ScrollPaginationProps } from 'reactjs-scroll-pagination';
const MyComponent: React.FC = () => {
const props: ScrollPaginationProps = {
loadMore: async () => { /* ... */ },
hasMore: true,
children: <div>Content</div>
};
return <ScrollPagination {...props} />;
};Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers
Requires support for IntersectionObserver API (available in all modern browsers).
Contributing
Contributions, issues and feature requests are welcome!
Author
- Website: https://tanmoy-paul.vercel.app
- Github: @tanmoypaul1005
Show your support
Give a ⭐️ if this project helped you!
📝 License
This project is ISC licensed.
This README was generated with ❤️ by readme-md-generator
