Infinite Scroll
A cross-platform infinite scroll component for Web and React Native with automatic loading, optional virtualization, and pull-to-refresh support.
Usage
📱 Interactive Demo: Scroll down to see more items load automatically!
Showcase infinite scroll with product catalogs and inventory management.
Perfect for displaying posts, comments, and user-generated content.
import { InfiniteScroll } from "@/components/infinite-scroll"
import { useState, useCallback } from "react"
export default function MyComponent() {
const [items, setItems] = useState([])
const [hasNext, setHasNext] = useState(true)
const [loading, setLoading] = useState(false)
const loadMore = useCallback(async () => {
setLoading(true)
try {
const response = await fetch(`/api/items?page=${Math.floor(items.length / 10) + 1}`)
const data = await response.json()
setItems(prev => [...prev, ...data.items])
setHasNext(data.hasMore)
} catch (error) {
console.error('Failed to load items:', error)
} finally {
setLoading(false)
}
}, [items.length])
return (
<InfiniteScroll
items={items}
hasNextPage={hasNext}
isLoading={loading}
onLoadMore={loadMore}
threshold={200}
initialLoad={true}
renderItem={(item, index) => (
<div key={item.id} className="p-4 border rounded-lg">
<h3 className="font-semibold">{item.title}</h3>
<p className="text-muted-foreground">{item.description}</p>
</div>
)}
loader={() => (
<div className="flex justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary" />
</div>
)}
endMessage={
<div className="text-center py-4 text-muted-foreground">
<p>You've reached the end! 🎉</p>
</div>
}
/>
)
}import { InfiniteScroll } from "@/components/infinite-scroll-rn"
import { useState, useCallback } from "react"
import { View, Text } from "react-native"
export default function MyScreen() {
const [items, setItems] = useState([])
const [hasNext, setHasNext] = useState(true)
const [loading, setLoading] = useState(false)
const loadMore = useCallback(async () => {
setLoading(true)
try {
const response = await fetch(`/api/items?page=${Math.floor(items.length / 10) + 1}`)
const data = await response.json()
setItems(prev => [...prev, ...data.items])
setHasNext(data.hasMore)
} catch (error) {
console.error('Failed to load items:', error)
} finally {
setLoading(false)
}
}, [items.length])
return (
<InfiniteScroll
items={items}
hasNextPage={hasNext}
isLoading={loading}
onLoadMore={loadMore}
threshold={0.5}
initialLoad={true}
renderItem={(item, index) => (
<View>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
</View>
)}
keyExtractor={(item) => item.id}
/>
)
}Installation
Install the Infinite Scroll component for your platform using your preferred package manager.
npx shadcn@latest add @rigidui/infinite-scrollPrerequisites
First, ensure you have React Native Reusables properly set up in your project. Follow the complete setup guide at React Native Reusables Installation.
Install Component
npx shadcn@latest add @rigidui/infinite-scroll-rnFeatures
The Infinite Scroll component is packed with powerful features designed to provide an exceptional user experience across both Web and React Native platforms.
Automatic Loading
Automatically loads more content when user scrolls near the bottom with smart load triggering across platforms.
Cross-Platform Support
Available for both Web and React Native with platform-specific optimizations using Intersection Observer for Web and FlatList for React Native.
Virtual Scrolling
Optional virtualization with TanStack Virtual for Web to handle massive datasets with thousands of items while maintaining smooth performance.
High Performance
Uses efficient rendering strategies - Intersection Observer for Web and FlatList optimizations for React Native for better performance and battery life.
Pull to Refresh
Built-in pull-to-refresh support for React Native with customizable refresh handlers and loading states.
Flexible Configuration
Customizable loading triggers, custom loaders, error states, and support for reverse loading with dual rendering modes.
When to Use Virtualization
Virtualization is recommended when dealing with large datasets to maintain optimal performance.
Use Regular Mode When:
- Working with less than 1000 items
- Items have varying heights that are hard to estimate
- You need complex CSS layouts or animations
Use Virtualized Mode When:
- Displaying 1000+ items
- Items have consistent or predictable heights
- Performance is critical for your use case
- Working with data tables or feeds with many entries
Props
InfiniteScroll (Web)
| Prop | Type | Default |
|---|---|---|
items | T[] | - |
hasNextPage | boolean | - |
isLoading | boolean | - |
onLoadMore | () => void | Promise<void> | - |
renderItem | (item: T, index: number) => React.ReactNode | - |
threshold? | number | 100 |
loader? | React.ComponentType | DefaultLoader |
endMessage? | React.ReactNode | Default end message |
errorMessage? | React.ReactNode | undefined |
className? | string | undefined |
itemClassName? | string | undefined |
reverse? | boolean | false |
initialLoad? | boolean | false |
scrollableTarget? | string | undefined |
virtualized? | boolean | false |
estimateSize? | () => number | () => 50 |
height? | number | 400 |
overscan? | number | 5 |
InfiniteScroll (React Native)
| Prop | Type | Default |
|---|---|---|
items | T[] | - |
hasNextPage | boolean | - |
isLoading | boolean | - |
onLoadMore | () => void | Promise<void> | - |
renderItem | (item: T, index: number) => React.ReactNode | - |
threshold? | number | 0.5 |
loader? | React.ComponentType | ActivityIndicator |
endMessage? | React.ReactNode | Default end message |
errorMessage? | React.ReactNode | undefined |
className? | string | undefined |
itemClassName? | string | undefined |
reverse? | boolean | false |
initialLoad? | boolean | false |
estimateSize? | number | 50 |
keyExtractor? | (item: T, index: number) => string | Uses index |
Advanced Examples
Explore different configurations and specialized use cases for the Infinite Scroll component across Web and React Native platforms.
Virtualized Infinite Scroll
Experience high-performance scrolling with thousands of items. Only visible items are rendered to the DOM, ensuring smooth performance regardless of dataset size.
Virtual Scrolling Active
Smooth rendering with virtualization
import { InfiniteScroll } from "@/components/infinite-scroll"
import { useState, useEffect } from "react"
export default function VirtualizedExample() {
const [items, setItems] = useState([])
const [hasNext, setHasNext] = useState(true)
const [loading, setLoading] = useState(false)
const loadMore = async () => {
if (loading) return
setLoading(true)
await new Promise(resolve => setTimeout(resolve, 600))
const newItems = Array.from({ length: 20 }, (_, i) => ({
id: items.length + i + 1,
title: `Product ${items.length + i + 1}`,
description: "High-quality product with detailed specifications and reviews",
price: Math.floor(Math.random() * 500) + 50,
category: ["Electronics", "Clothing", "Home", "Sports"][Math.floor(Math.random() * 4)]
}))
setItems(prev => [...prev, ...newItems])
setHasNext(items.length + newItems.length < 500)
setLoading(false)
}
useEffect(() => {
loadMore()
}, [])
return (
<InfiniteScroll
items={items}
hasNextPage={hasNext}
isLoading={loading}
onLoadMore={loadMore}
renderItem={(item) => (
<div className="group p-6 border border-border rounded-xl bg-card hover:shadow-md transition-all duration-200">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="font-semibold text-lg text-foreground group-hover:text-primary transition-colors">
{item.title}
</h3>
<span className="text-xs bg-muted text-muted-foreground px-2 py-1 rounded-full">
{item.category}
</span>
</div>
<p className="text-muted-foreground text-sm leading-relaxed mb-3">
{item.description}
</p>
<div className="flex items-center justify-between">
<span className="text-xl font-bold text-primary">
${item.price}
</span>
<div className="flex gap-1">
{[...Array(5)].map((_, i) => (
<div key={i} className="w-3 h-3 bg-yellow-400 rounded-full text-xs"></div>
))}
</div>
</div>
</div>
</div>
</div>
)}
virtualized={true}
height={400}
estimateSize={() => 120}
overscan={3}
className="p-4"
/>
)
}