Skip to content
intermediatePhase 37 · Frontend Architecture

Pagination

Build offset and cursor-based pagination with proper state management.

45m
0 problems
Topic Progress0%

Offset Pagination

Offset Pagination

Offset pagination uses page numbers and items per page.

Basic Implementation

// hooks/usePagination.js
function usePagination({ totalItems, itemsPerPage = 10, initialPage = 1 }) {
  const [currentPage, setCurrentPage] = useState(initialPage);
  
  const totalPages = Math.ceil(totalItems / itemsPerPage);
  const offset = (currentPage - 1) * itemsPerPage;
  
  const goToPage = (page) => {
    const pageNumber = Math.max(1, Math.min(page, totalPages));
    setCurrentPage(pageNumber);
  };
  
  const goToNextPage = () => goToPage(currentPage + 1);
  const goToPreviousPage = () => goToPage(currentPage - 1);
  const goToFirstPage = () => goToPage(1);
  const goToLastPage = () => goToPage(totalPages);
  
  // Generate page numbers
  const getPageNumbers = () => {
    const pages = [];
    const maxVisible = 5;
    
    let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
    let end = Math.min(totalPages, start + maxVisible - 1);
    
    if (end - start + 1 < maxVisible) {
      start = Math.max(1, end - maxVisible + 1);
    }
    
    for (let i = start; i <= end; i++) {
      pages.push(i);
    }
    
    return pages;
  };
  
  return {
    currentPage,
    totalPages,
    offset,
    itemsPerPage,
    goToPage,
    goToNextPage,
    goToPreviousPage,
    goToFirstPage,
    goToLastPage,
    getPageNumbers,
    hasNextPage: currentPage < totalPages,
    hasPreviousPage: currentPage > 1,
  };
}

Pagination Component

// components/Pagination.jsx
function Pagination({ currentPage, totalPages, onPageChange }) {
  const pages = getPageNumbers(currentPage, totalPages);
  
  return (
    <nav className="pagination" aria-label="Pagination">
      <button
        onClick={() => onPageChange(1)}
        disabled={currentPage === 1}
        aria-label="First page"
      >
        «
      </button>
      <button
        onClick={() => onPageChange(currentPage - 1)}
        disabled={currentPage === 1}
        aria-label="Previous page"
      >
        ‹
      </button>
      
      {pages.map(page => (
        <button
          key={page}
          onClick={() => onPageChange(page)}
          className={page === currentPage ? 'active' : ''}
          aria-current={page === currentPage ? 'page' : undefined}
        >
          {page}
        </button>
      ))}
      
      <button
        onClick={() => onPageChange(currentPage + 1)}
        disabled={currentPage === totalPages}
        aria-label="Next page"
      >
        ›
      </button>
      <button
        onClick={() => onPageChange(totalPages)}
        disabled={currentPage === totalPages}
        aria-label="Last page"
      >
        »
      </button>
    </nav>
  );
}

API Integration

function ProductList() {
  const [page, setPage] = useState(1);
  const [pageSize] = useState(20);
  
  const { data, isLoading } = useQuery({
    queryKey: ['products', page, pageSize],
    queryFn: () => fetchProducts({ page, pageSize }),
    keepPreviousData: true,
  });
  
  return (
    <div>
      {isLoading && <Spinner />}
      <ProductTable products={data?.items || []} />
      <Pagination
        currentPage={page}
        totalPages={data?.totalPages || 0}
        onPageChange={setPage}
      />
    </div>
  );
}

Pros and Cons

Pros:

  • Simple to implement
  • Direct page access
  • URL-friendly

Cons:

  • Skips items on page changes
  • Inconsistent with real-time data
  • Performance issues with large offsets

Cursor Pagination

Cursor Pagination

Cursor pagination uses a pointer to the last item instead of page numbers.

How It Works

GET /api/products?limit=20&cursor=abc123

Response:
{
  "items": [...],
  "nextCursor": "xyz789",
  "hasMore": true
}

Implementation

// hooks/useCursorPagination.js
function useCursorPagination(queryKey, fetchFn, { limit = 20 } = {}) {
  const [cursors, setCursors] = useState(['']);
  const [direction, setDirection] = useState('next');
  
  const currentCursor = cursors[cursors.length - 1];
  
  const { data, isLoading, isFetching } = useQuery({
    queryKey: [...queryKey, currentCursor, limit],
    queryFn: () => fetchFn({ cursor: currentCursor, limit }),
    keepPreviousData: true,
  });
  
  const goToNextPage = () => {
    if (data?.nextCursor) {
      setCursors(prev => [...prev, data.nextCursor]);
      setDirection('next');
    }
  };
  
  const goToPreviousPage = () => {
    if (cursors.length > 1) {
      setCursors(prev => prev.slice(0, -1));
      setDirection('prev');
    }
  };
  
  return {
    items: data?.items || [],
    isLoading,
    isFetching,
    hasNextPage: data?.hasMore || false,
    hasPreviousPage: cursors.length > 1,
    goToNextPage,
    goToPreviousPage,
    totalItems: data?.total,
  };
}

// Component
function ProductList() {
  const {
    items,
    isLoading,
    hasNextPage,
    hasPreviousPage,
    goToNextPage,
    goToPreviousPage,
  } = useCursorPagination(['products'], fetchProducts);
  
  return (
    <div>
      {isLoading ? (
        <Spinner />
      ) : (
        <>
          <ProductTable products={items} />
          <div className="pagination">
            <button onClick={goToPreviousPage} disabled={!hasPreviousPage}>
              Previous
            </button>
            <button onClick={goToNextPage} disabled={!hasNextPage}>
              Next
            </button>
          </div>
        </>
      )}
    </div>
  );
}

Pros and Cons

Pros:

  • Consistent with real-time data
  • Better performance for large datasets
  • No skipped/duplicate items

Cons:

  • No direct page access
  • More complex implementation
  • Cannot jump to specific page

Infinite Scroll vs Pagination

Infinite Scroll vs Pagination

When to Use Each

Use Case Recommended
E-commerce product listing Pagination
Social media feed Infinite scroll
Search results Pagination
Chat messages Infinite scroll
Admin dashboards Pagination
Image galleries Infinite scroll

Pagination Component

function PaginatedList() {
  const [page, setPage] = useState(1);
  const { data, isLoading } = useQuery({
    queryKey: ['items', page],
    queryFn: () => fetchItems({ page, limit: 20 }),
  });
  
  return (
    <div>
      <List items={data?.items} />
      <Pagination
        current={page}
        total={data?.total}
        onChange={setPage}
      />
      {/* SEO-friendly, accessible */}
      {/* Users can jump to specific pages */}
      {/* URL can reflect page state */}
    </div>
  );
}

Infinite Scroll Component

function InfiniteList() {
  const {
    items,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['items'],
    queryFn: ({ pageParam }) => fetchItems({ cursor: pageParam }),
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });
  
  const allItems = items?.pages.flatMap(page => page.items) || [];
  
  return (
    <div>
      <List items={allItems} />
      {hasNextPage && (
        <button
          onClick={() => fetchNextPage()}
          disabled={isFetchingNextPage}
        >
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}

User Experience Considerations

Pagination Pros:

  • Users can navigate to specific pages
  • Clear sense of progress
  • Better for data analysis
  • URL shareable

Infinite Scroll Pros:

  • More engaging browsing
  • Better for content discovery
  • Less clicking required
  • Better mobile experience

Infinite Scroll Cons:

  • Hard to return to specific position
  • Footer/links hard to reach
  • Performance issues with many items
  • Accessibility challenges

Practice Problems

0/3solved
Build Pagination Component

Create a reusable React component implementing Pagination. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Pagination Testing

Write unit and integration tests for Pagination using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Pagination Performance

Optimize Pagination for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What is the main issue with offset pagination on large datasets?

Question 1 options

2. What is the key advantage of cursor pagination?

Question 2 options

3. When is infinite scroll most appropriate?

Question 3 options

4. Why is pagination better for SEO?

Question 4 options

5. What should you use to maintain scroll position with infinite scroll?

Question 5 options

Flashcards

Question

What is offset pagination?

Answer

Pagination that uses page numbers and items-per-page to calculate the starting position.

Question

What is cursor pagination?

Answer

Pagination that uses a pointer to the last item for consistent, reliable page navigation.

Question

When should you use pagination over infinite scroll?

Answer

For admin dashboards, search results, and scenarios where users need to navigate to specific positions.

Question

What is the main disadvantage of offset pagination?

Answer

Items can be skipped or duplicated when data changes between page requests.

Question

What is Pagination?

Answer

Pagination is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.Offset pagination is simple but can skip items with real-time data
  • 2.Cursor pagination provides consistent results for large datasets
  • 3.Choose pagination for navigation-heavy UIs, infinite scroll for discovery
  • 4.Always consider accessibility and SEO when choosing pagination strategy
  • 5.Maintain URL state for shareable pagination positions

Interview Tips

  • Explain the difference between offset and cursor pagination
  • Discuss when to use pagination vs infinite scroll
  • Know the trade-offs of each approach

Cheat Sheet

Pagination Cheat Sheet

Offset Pagination

const offset = (page - 1) * limit;
fetch(`/api/items?offset=${offset}&limit=${limit}`);

Cursor Pagination

fetch(`/api/items?cursor=${lastId}&limit=${limit}`);

When to Use

  • Pagination: Admin dashboards, search, data tables
  • Infinite Scroll: Social feeds, image galleries, content discovery

Key Considerations

  • Offset can skip items with real-time data
  • Cursor provides consistent results
  • Infinite scroll needs position management