Pagination

Build a pagination component using React with page navigation, data chunking, and user-friendly controls

Pagination - Machine Coding Question

Problem Statement

Create a pagination component that allows users to:

  • View data in manageable chunks (pages)
  • Navigate between pages using controls
  • Display current page status
  • Handle large datasets efficiently
  • Provide intuitive navigation experience

🧠 Understanding the Problem

Think of pagination like a book with many pages. Instead of showing all the content at once (which would be overwhelming), we show one page at a time and provide navigation controls to move between pages.

Why Do We Need Pagination?

  1. Performance: Loading 1000 items at once is slow and uses lots of memory
  2. User Experience: Users can focus on a smaller set of items
  3. Navigation: Easy to find specific items
  4. Mobile Friendly: Better for smaller screens

🏗️ System Design & Architecture

Core Concepts

1. Page Size

The number of items shown per page (e.g., 10 items per page)

2. Total Pages

Calculated as: Math.ceil(totalItems / pageSize)

3. Current Page

The page currently being displayed

4. Navigation Controls

  • Previous/Next buttons
  • Page numbers
  • Ellipsis for large page counts

Component Architecture

App
├── ProductList (displays current page items)
└── Pagination
    ├── Previous Button
    ├── Page Numbers
    └── Next Button

Step-by-Step Implementation

Step 1: Project Setup

First, let's create our React project:

npx create-react-app pagination-demo
cd pagination-demo

Step 2: Understanding the Data Flow

How Pagination Works:

  1. Fetch Data: Get all items from API
  2. Calculate Pages: Determine how many pages we need
  3. Slice Data: Show only items for current page
  4. Handle Navigation: Update current page when user clicks

Step 3: Building the Main Component

Create src/App.js:

import "./styles.css";
import { useState, useEffect } from "react";

export default function App() {
  // State for storing all products
  const [products, setProducts] = useState([]);
  
  // State for current page (starts at page 1)
  const [page, setPage] = useState(1);

  // Fetch products from API
  const fetchProducts = async () => {
    try {
      const res = await fetch("https://dummyjson.com/products?limit=100");
      const data = await res.json();
      
      if (data.products && data.products.length) {
        setProducts(data.products);
      }
    } catch (error) {
      console.error("Error fetching products:", error);
    }
  };

  // Fetch products when component mounts
  useEffect(() => {
    fetchProducts();
  }, []);

  // Handle page change
  const handlePageChange = (pageNumber) => {
    const totalPages = Math.ceil(products.length / 10);
    
    // Validate page number
    if (
      pageNumber > 0 && 
      pageNumber <= totalPages && 
      pageNumber !== page
    ) {
      setPage(pageNumber);
    }
  };

  return (
    <div className="App">
      <h1>🛍️ All Products</h1>
      
      {/* Display products for current page */}
      {products.length > 0 && (
        <ol className="All__products">
          {products
            .slice((page - 1) * 10, page * 10)
            .map((product) => (
              <li key={product.id} className="product">
                <img src={product.thumbnail} alt={product.title} />
                <h4>{product.title}</h4>
                <p>${product.price}</p>
              </li>
            ))}
        </ol>
      )}

      {/* Pagination controls */}
      {products.length > 0 && (
        <section className="pagination">
          {/* Previous button */}
          <span
            onClick={() => handlePageChange(page - 1)}
            className={`arrow ${page === 1 ? "pagination__disabled" : ""}`}
          >
            ⬅ Previous
          </span>

          {/* Page numbers */}
          {[...Array(Math.ceil(products.length / 10))].map((_, i) => (
            <span
              className={`page__number ${
                page === i + 1 ? "selected__page__number" : ""
              }`}
              key={i + 1}
              onClick={() => handlePageChange(i + 1)}
            >
              {i + 1}
            </span>
          ))}

          {/* Next button */}
          <span
            onClick={() => handlePageChange(page + 1)}
            className={`arrow ${
              page === Math.ceil(products.length / 10)
                ? "pagination__disabled"
                : ""
            }`}
          >
            Next ➡
          </span>
        </section>
      )}
    </div>
  );
}

Step 4: Understanding the Key Logic

Data Slicing Formula

// Show items from index (page - 1) * 10 to page * 10
products.slice((page - 1) * 10, page * 10)

Example:

  • Page 1: Items 0-9 (index 0 to 9)
  • Page 2: Items 10-19 (index 10 to 19)
  • Page 3: Items 20-29 (index 20 to 29)

Page Number Generation

// Create array with length equal to total pages
[...Array(Math.ceil(products.length / 10))]

Example:

  • 100 products ÷ 10 per page = 10 pages
  • Creates array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • Maps to page numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Step 5: Styling Our Component

Create src/styles.css:

.App {
  text-align: center;
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}

h1 {
  color: #333;
  margin-bottom: 30px;
}

.All__products {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  list-style: none;
  padding: 0;
  margin: 20px 0;
}

.product {
  background: white;
  border-radius: 8px;
  padding: 15px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s;
}

.product:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

.product img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 4px;
  margin-bottom: 10px;
}

.product h4 {
  margin: 10px 0;
  color: #333;
  font-size: 16px;
}

.product p {
  color: #666;
  font-weight: bold;
  margin: 5px 0;
}

/* Pagination Styles */
.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-top: 30px;
  flex-wrap: wrap;
}

.page__number {
  padding: 8px 12px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
  transition: all 0.2s;
  min-width: 40px;
  text-align: center;
}

.page__number:hover {
  background: #f0f0f0;
  border-color: #999;
}

.selected__page__number {
  background: #007bff;
  color: white;
  border-color: #007bff;
}

.selected__page__number:hover {
  background: #0056b3;
}

.arrow {
  padding: 8px 12px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
  transition: all 0.2s;
}

.arrow:hover {
  background: #f0f0f0;
  border-color: #999;
}

.pagination__disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.pagination__disabled:hover {
  background: white;
  border-color: #ddd;
}

Key Features Explained

1. State Management

  • products: Stores all fetched data
  • page: Tracks current page number
  • Both states work together to show correct data

2. Data Fetching

  • Uses useEffect to fetch data on component mount
  • Handles API errors gracefully
  • Stores all data in state for client-side pagination

3. Page Calculation

  • Math.ceil(products.length / 10): Calculates total pages
  • Ensures we have enough pages for all items

4. Data Slicing

  • slice((page - 1) * 10, page * 10): Shows only current page items
  • Zero-based indexing handled correctly

5. Navigation Logic

  • Validates page numbers before changing
  • Prevents invalid page navigation
  • Handles edge cases (first/last page)

🧩 Understanding the Code Flow

  1. Component Mounts: useEffect triggers data fetch
  2. Data Received: Products stored in state
  3. Initial Render: Shows page 1 with first 10 items
  4. User Clicks Page: handlePageChange validates and updates page
  5. Re-render: Component shows new page's items
  6. Navigation: Previous/Next buttons work with validation

Running the Application

npm start

Your pagination app will be running at http://localhost:3000!

What You've Learned

  1. Data Chunking: How to split large datasets into pages
  2. State Management: Managing multiple related states
  3. Array Manipulation: Using slice and map for pagination
  4. User Interface: Creating intuitive navigation controls
  5. API Integration: Fetching and handling external data

Advanced Pagination Features

1. Ellipsis for Large Page Counts

const renderPageNumbers = () => {
  const totalPages = Math.ceil(products.length / 10);
  const pages = [];
  
  if (totalPages <= 7) {
    // Show all pages
    for (let i = 1; i <= totalPages; i++) {
      pages.push(i);
    }
  } else {
    // Show first 3, last 3, and current with ellipsis
    pages.push(1, 2, 3, '...', totalPages - 2, totalPages - 1, totalPages);
  }
  
  return pages;
};

2. Dynamic Page Size

const [pageSize, setPageSize] = useState(10);

// Allow users to change items per page
const handlePageSizeChange = (newSize) => {
  setPageSize(newSize);
  setPage(1); // Reset to first page
};

3. Server-Side Pagination

const fetchProducts = async (pageNumber, pageSize) => {
  const res = await fetch(
    `https://api.example.com/products?page=${pageNumber}&limit=${pageSize}`
  );
  const data = await res.json();
  return data;
};

Tips for Interview

  1. Start Simple: Begin with basic page navigation
  2. Add Features Incrementally: Add ellipsis, page size, etc.
  3. Handle Edge Cases: Empty data, single page, invalid navigation
  4. Performance: Consider server-side pagination for large datasets
  5. Accessibility: Add ARIA labels and keyboard navigation

🎉 Conclusion

Congratulations! You've built a functional pagination component that demonstrates:

  • Efficient data handling
  • User-friendly navigation
  • Responsive design
  • State management best practices

This pagination pattern is used everywhere:

  • E-commerce product listings
  • Blog post archives
  • Search results
  • Social media feeds
  • Admin dashboards

The concepts you've learned apply to any scenario where you need to display large amounts of data in manageable chunks. Keep building and exploring!

  • Infinite Scroll: Alternative to pagination
  • Virtual Scrolling: For very large lists
  • Lazy Loading: Loading data as needed
  • Caching: Storing fetched data
  • Search & Filter: Combining with pagination