Pagination and Infinite Scroll
This example demonstrates various pagination patterns: offset-based, cursor-based, infinite scroll, and load-more button.
Running the Example
bash
npx ts-node index.tsPatterns Demonstrated
1. Offset-based Pagination (Traditional)
Best for: Traditional page numbers (1, 2, 3...)
typescript
const products = new DataSource<Product>({
url: '/api/products',
pagination: {
enabled: true,
pageSize: 20,
strategy: 'offset'
}
});
// Navigate pages
await products.fetch(); // Page 1
await products.fetchMore(); // Page 2
await products.fetchPage(5); // Jump to page 52. Cursor-based Pagination (Efficient)
Best for: Real-time feeds, large datasets
typescript
const products = new DataSource<Product>({
pagination: {
strategy: 'cursor',
cursorField: 'id',
pageSize: 20
}
});
// Load next batch
await products.fetchMore(); // Uses last ID as cursor3. Infinite Scroll
Best for: Mobile apps, social feeds
typescript
const handleScroll = throttle(async () => {
const nearBottom =
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight - 500;
if (nearBottom && products.hasMore && !products.loading) {
await products.fetchMore();
}
}, 200);
window.addEventListener('scroll', handleScroll);4. Load More Button
Best for: Controlled loading, better UX
typescript
async function loadMore() {
if (products.hasMore) {
await products.fetchMore();
if (!products.hasMore) {
hideLoadMoreButton();
}
}
}Pagination State
Access pagination info:
typescript
products.currentPage // Current page number
products.totalPages // Total pages available
products.totalRecords // Total records count
products.hasMore // More data available?
products.loading // Currently loading?
products.nextCursor // Next cursor (cursor pagination)With Filters and Search
typescript
// Search with pagination
await products.fetch({
search: 'laptop',
category: 'electronics',
minPrice: 100,
maxPrice: 1000
});
// Load more with same filters
await products.fetchMore(); // Maintains filtersCaching Strategy
typescript
const products = new DataSource<Product>({
pagination: {
enabled: true,
pageSize: 20
},
cache: {
enabled: true,
ttl: 300000 // 5 minutes
}
});
// First fetch: hits API
await products.fetch();
// Within 5 min: uses cache
await products.fetch();
// After 5 min: hits API againPerformance Tips
Use throttle for scroll events
typescriptimport { throttle } from 'bindra'; const handleScroll = throttle(loadMore, 200);Set appropriate page sizes
- Infinite scroll: 20-30 items
- Traditional pagination: 50-100 items
- Mobile: 10-15 items
Cache aggressively
typescriptcache: { enabled: true, ttl: 600000 } // 10 minShow loading states
typescriptproducts.loadingState.subscribe(isLoading => { if (isLoading) showSpinner(); });
React Integration Example
typescript
import { useEffect, useState } from 'react';
import { DataSource } from 'bindra';
function ProductList() {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const ds = useMemo(() => new DataSource<Product>({
url: '/api/products',
pagination: { enabled: true, pageSize: 20 }
}), []);
useEffect(() => {
// Subscribe to data changes
ds.dataSource.subscribe(setProducts);
ds.loadingState.subscribe(setLoading);
// Load initial data
ds.fetch();
return () => ds.disconnectWebSocket();
}, []);
const loadMore = () => {
if (ds.hasMore && !loading) {
ds.fetchMore();
}
};
return (
<div>
{products.map(p => <ProductCard key={p.id} {...p} />)}
{ds.hasMore && (
<button onClick={loadMore} disabled={loading}>
{loading ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}