React Pagination: Front End Machine Coding Interview Question

React Pagination: Front End Machine Coding Interview Question

Pagination is a critical concept in web development, especially for managing large datasets displayed on web pages. Implementing pagination not only improves performance by reducing data load but also enhances user experience by organizing information into manageable chunks. If you’re preparing for machine coding interviews, especially for roles involving front-end technologies like React, understanding how to implement pagination can set you apart.

In this blog, we’ll explore how to build a React pagination component and integrate it with a dataset fetched from an API. We’ll break down the code into two main files—Pagination.js and App.js—and explain how every part of the code works. By the end, you’ll have a solid understanding of React pagination and be ready to tackle similar challenges in interviews or real-world projects.

Why Use Pagination?

  1. Better Performance – Reduces memory and CPU usage by loading only a subset of data.
  2. Improved UX – Makes it easier for users to browse data step-by-step.
  3. SEO Benefits – Helps search engines index content effectively.

The Pagination Component

The Pagination.js file is responsible for rendering the pagination controls, including “Previous” and “Next” buttons and a list of page numbers. Let’s explore its code step by step.

Code Walkthrough: Pagination.js

import React from "react";

const Pagination = React.memo(({ totalPages, currentPage, onPageChange }) => {
  // Step 1: Create an array of page numbers based on the total number of pages.
  const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);

  return (
    <div className="pagination">
      {/* Step 2: Previous Button */}
      <button
        disabled={currentPage === 1} // Disable the button if the current page is the first page.
        onClick={() => onPageChange(currentPage - 1)} // Go to the previous page.
      >
        Previous
      </button>

      {/* Step 3: Page Numbers */}
      {pageNumbers.map((num) => (
        <button
          key={num} // Unique key for each button.
          className={currentPage === num ? "active" : ""} // Highlight the active page.
          onClick={() => onPageChange(num)} // Change to the selected page.
        >
          {num}
        </button>
      ))}

      {/* Step 4: Next Button */}
      <button
        disabled={currentPage === totalPages} // Disable the button if the current page is the last page.
        onClick={() => onPageChange(currentPage + 1)} // Go to the next page.
      >
        Next
      </button>
    </div>
  );
});

export default Pagination;

Explanation of Each Part:

  1. React.memo:
    • This is a higher-order component that prevents unnecessary re-renders by memoizing the component. It re-renders only if the totalPages, currentPage, or onPageChange props change.
  2. pageNumbers Array:
    • Uses Array.from to generate an array of numbers from 1 to totalPages. This array is used to dynamically create page number buttons.
  3. Navigation Buttons:
    • The “Previous” button is disabled if the user is on the first page (currentPage === 1).
    • The “Next” button is disabled if the user is on the last page (currentPage === totalPages).
    • Each button uses the onPageChange callback to notify the parent component of the new page number.
  4. Active Page Styling:
    • Adds a CSS class (active) to the button representing the current page, allowing for visual distinction.

The App Component

The App.js file serves as the main application, fetching data from an API and displaying it along with the pagination controls. Let’s go through it step by step.

import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import Pagination from "./Pagination";
import "./styles.css"; // Importing the styles

const App = () => {
  // Step 1: Initialize state variables.
  const [data, setData] = useState([]); // Holds the data fetched from the API.
  const [currentPage, setCurrentPage] = useState(1); // Tracks the current page number.
  const [totalPages, setTotalPages] = useState(0); // Stores the total number of pages.
  const [loading, setLoading] = useState(false); // Indicates whether data is being fetched.
  const itemsPerPage = 10; // Number of items to display per page.

  // Step 2: Define a debounce function to delay rapid updates.
  const debounce = (func, delay) => {
    let timer;
    return (...args) => {
      clearTimeout(timer); // Clear the previous timer.
      timer = setTimeout(() => func(...args), delay); // Set a new timer.
    };
  };

  // Step 3: Debounced handler for page changes.
  const handlePageChange = useCallback(
    debounce((page) => {
      setCurrentPage(page); // Update the current page.
    }, 300),
    []
  );

  // Step 4: Fetch data whenever the current page changes.
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true); // Set loading state to true.
      try {
        // Fetch data from the API with pagination parameters.
        const res = await axios.get(
          `https://dummyjson.com/products?limit=${itemsPerPage}&skip=${(currentPage - 1) * itemsPerPage}`
        );
        setData(res.data.products); // Update the data state.
        setTotalPages(Math.ceil(res.data.total / itemsPerPage)); // Calculate and update total pages.
      } catch (error) {
        console.error("Error fetching data:", error); // Handle errors.
      } finally {
        setLoading(false); // Reset loading state.
      }
    };

    fetchData();
  }, [currentPage]); // Re-run the effect whenever `currentPage` changes.

  return (
    <div className="App">
      <h1>React Pagination Example</h1>

      {/* Step 5: Display loading message or data. */}
      {loading ? (
        <p>Loading...</p> // Show loading message.
      ) : (
        <ul className="product-list">
          {/* Render the list of products. */}
          {data.map((item) => (
            <li key={item.id} className="product-item">
              <img src={item.thumbnail} alt={item.title} className="product-image" />
              <div className="product-details">
                <h3 className="product-title">{item.title}</h3>
                <p className="product-price">${item.price}</p>
              </div>
            </li>
          ))}
        </ul>
      )}

      {/* Step 6: Render the Pagination component. */}
      <Pagination
        totalPages={totalPages} // Pass total pages to the component.
        currentPage={currentPage} // Pass the current page to the component.
        onPageChange={handlePageChange} // Pass the page change handler.
      />
    </div>
  );
};

export default App;

Explanation of Each Part:

  1. API URL:
`https://dummyjson.com/products?limit=${itemsPerPage}&skip=${(currentPage - 1) * itemsPerPage}`
  • The limit parameter specifies the number of items to fetch per request.
  • The skip parameter calculates the number of items to skip, based on the current page. It is derived as (currentPage – 1) * itemsPerPage.
  1. Math.ceil** for Total Pages**:
    • Math.ceil is used to ensure the total number of pages is rounded up. For example, if there are 101 items and 10 items per page, 101 / 10 equals 10.1. Using Math.ceil, it rounds up to 11 pages.
  2. Debouncing:
    • Prevents multiple rapid API calls when the user clicks pagination buttons too quickly. Debouncing delays the setCurrentPage call until 300ms after the last interaction, improving performance.
  3. <ul>** with Images**:
<ul className="product-list">
  {data.map((item) => (
    <li key={item.id} className="product-item">
      <img src={item.thumbnail} alt={item.title} className="product-image" />
      <div className="product-details">
        <h3 className="product-title">{item.title}</h3>
        <p className="product-price">${item.price}</p>
      </div>
    </li>
  ))}
</ul>
  • Displays each product’s thumbnail image along with its title and price. The img tag uses the product’s thumbnail property as the source.
  • Additional CSS classes (product-list, product-item, etc.) enhance styling and layout.
  1. CSS Classes:
    • .pagination: Styles the pagination container.
    • .product-list: Adds styling for the product list container.
    • .product-item: Styles each individual product.
    • .product-image: Controls the size and display of product images.
    • .product-details: Provides layout for titles and prices.
    • .product-title: Adds font styling for product titles.
    • .product-price: Styles the pricing display.

How All Components Work Together

When the user interacts with the pagination controls, the following sequence occurs:

  1. Button Click:
    • Clicking a page number, “Previous,” or “Next” button triggers the onPageChange function passed from App.js to Pagination.js.
    • The debounce function ensures that rapid interactions do not overload the app with multiple state updates or API calls.
  2. State Update:
    • The setCurrentPage state update triggers a re-render of the App.js component.
    • The updated currentPage value is passed to the Pagination component and the API fetch logic in useEffect.
  3. State Update:
    • The setCurrentPage state update triggers a re-render of the App.js component.
    • The updated currentPage value is passed to the Pagination component and the API fetch logic in useEffect.
  4. API Call:
    • The useEffect hook detects the change in currentPage and triggers the fetchData function.
    • The fetchData function retrieves the relevant page of products using axios and updates the data and totalPages states.
  5. Render Updated Data:
    • The updated data state triggers a re-render of the <ul> list in the App.js component.
    • The Pagination component reflects the active page and disables the “Previous” or “Next” buttons when appropriate.

Styling the Pagination Component

To enhance user experience, you can add styles to the pagination component. Below is an example using CSS:

/* General Styles */
body {
  font-family: Arial, sans-serif;
  background-color: #f9f9f9;
  margin: 0;
  padding: 0;
}

/* Pagination Styles */
.pagination {
  display: flex;
  flex-wrap: wrap; /* Ensures the buttons wrap on smaller screens */
  justify-content: center;
  gap: 8px;
  margin: 20px 0;
}

.pagination button {
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  background-color: white;
  cursor: pointer;
  transition: all 0.3s ease;
  font-size: 14px;
}

.pagination button:hover {
  background-color: #f0f0f0;
}

.pagination button.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.pagination button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* Responsive Styles for Pagination */
@media (max-width: 768px) {
  .pagination button {
    flex: 1 1 100%;
    max-width: 150px;
    text-align: center;
  }

  .pagination {
    gap: 4px;
  }
}

/* Product List Styles */
.product-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.product-item {
  border: 1px solid #ddd;
  padding: 16px;
  text-align: center;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

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

.product-image {
  max-width: 100%;
  height: auto;
  border-radius: 4px;
  margin-bottom: 12px;
}

.product-title {
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 8px;
}

.product-price {
  font-size: 14px;
  color: #555;
}

/* Responsive Design */
@media (max-width: 600px) {
  .product-item {
    padding: 12px;
  }

  .product-title {
    font-size: 14px;
  }

  .product-price {
    font-size: 12px;
  }
}  

Here is the output

Conclusion

This React pagination implementation demonstrates key concepts like state management, API integration, and performance optimization using React.memo and debouncing. The modular structure makes the solution easy to maintain and extend.

Understanding this approach equips you to tackle similar challenges in coding interviews and real-world applications. Modify the code, experiment with different datasets, and make it your own!

Leave a Comment