Skip to content

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.ts

Patterns 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 5

2. 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 cursor

3. 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)
typescript
// Search with pagination
await products.fetch({
  search: 'laptop',
  category: 'electronics',
  minPrice: 100,
  maxPrice: 1000
});

// Load more with same filters
await products.fetchMore(); // Maintains filters

Caching 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 again

Performance Tips

  1. Use throttle for scroll events

    typescript
    import { throttle } from 'bindra';
    const handleScroll = throttle(loadMore, 200);
  2. Set appropriate page sizes

    • Infinite scroll: 20-30 items
    • Traditional pagination: 50-100 items
    • Mobile: 10-15 items
  3. Cache aggressively

    typescript
    cache: { enabled: true, ttl: 600000 } // 10 min
  4. Show loading states

    typescript
    products.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>
  );
}

Learn More

Released under the MIT License.