Handling API Pagination Properly

Loading

API pagination is a common technique used by APIs to break large sets of data into smaller, manageable chunks, or “pages.” This is especially important when dealing with a large number of records, such as in a list of items, search results, or logs. Proper handling of pagination can improve performance, provide better user experiences, and ensure efficient data management in React applications.

When you work with paginated APIs, you will need to handle pagination logic on both the front-end and the back-end. In a React app, this typically involves fetching data for each page and updating the UI accordingly.


1. Understanding API Pagination

APIs paginate data in various ways. The most common methods include:

  • Offset-based Pagination: You request a specific “page” of data by passing an offset (i.e., the starting point) and a limit (i.e., how many items to fetch). Example: GET /api/items?offset=0&limit=20
  • Cursor-based Pagination: Instead of offset and limit, cursor-based pagination uses a cursor (typically an ID or timestamp) that points to the last item retrieved. It provides more efficient pagination for large datasets. Example: GET /api/items?cursor=xyz123&limit=20

2. Handling Offset-based Pagination in React

Here is an example of how to implement offset-based pagination in React. The component fetches data in pages based on the current page and displays the result.

Example: Implementing Offset Pagination in React

import React, { useState, useEffect } from 'react';

const PaginatedList = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);

  const ITEMS_PER_PAGE = 20;

  const fetchData = async (page) => {
    setLoading(true);
    setError(null);
    try {
      const offset = (page - 1) * ITEMS_PER_PAGE;
      const response = await fetch(`https://api.example.com/items?offset=${offset}&limit=${ITEMS_PER_PAGE}`);
      const result = await response.json();
      setData(result.items); // Assuming `items` contains the paginated data
      setTotalPages(Math.ceil(result.totalItems / ITEMS_PER_PAGE)); // Assuming `totalItems` is the total count of items
    } catch (err) {
      setError('Failed to fetch data');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData(currentPage);
  }, [currentPage]);

  const handleNext = () => {
    if (currentPage < totalPages) setCurrentPage(currentPage + 1);
  };

  const handlePrev = () => {
    if (currentPage > 1) setCurrentPage(currentPage - 1);
  };

  return (
    <div>
      <h1>Paginated List</h1>
      {loading && <p>Loading...</p>}
      {error && <p>{error}</p>}
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li> // Assuming `id` and `name` are properties of each item
        ))}
      </ul>
      <div>
        <button onClick={handlePrev} disabled={currentPage === 1}>
          Previous
        </button>
        <span>{`Page ${currentPage} of ${totalPages}`}</span>
        <button onClick={handleNext} disabled={currentPage === totalPages}>
          Next
        </button>
      </div>
    </div>
  );
};

export default PaginatedList;

Explanation:

  • State Management: We use useState to manage data, loading state, error, current page, and total pages.
  • Pagination Logic: The fetchData function calculates the offset based on the current page and fetches the data for that page. The totalPages is calculated based on the total number of items returned by the API.
  • Pagination Controls: Two buttons (“Next” and “Previous”) allow the user to navigate between pages. The buttons are disabled when the user is on the first or last page.

3. Handling Cursor-based Pagination in React

For cursor-based pagination, you generally need a cursor value that indicates where the next set of data should start. This is more efficient than offset-based pagination, especially when dealing with a large number of records.

Example: Implementing Cursor-based Pagination in React

import React, { useState, useEffect } from 'react';

const CursorPaginatedList = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [cursor, setCursor] = useState(null); // Store the cursor for the next page
  const [hasMore, setHasMore] = useState(true);

  const ITEMS_PER_PAGE = 20;

  const fetchData = async () => {
    setLoading(true);
    setError(null);
    try {
      const url = cursor 
        ? `https://api.example.com/items?cursor=${cursor}&limit=${ITEMS_PER_PAGE}`
        : `https://api.example.com/items?limit=${ITEMS_PER_PAGE}`;
      const response = await fetch(url);
      const result = await response.json();
      setData(prevData => [...prevData, ...result.items]);
      setCursor(result.nextCursor); // Assuming `nextCursor` is returned by the API
      setHasMore(result.hasMore); // Indicates whether there are more pages
    } catch (err) {
      setError('Failed to fetch data');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, []); // Initial fetch on component mount

  const handleLoadMore = () => {
    if (hasMore) fetchData();
  };

  return (
    <div>
      <h1>Cursor-based Paginated List</h1>
      {loading && <p>Loading...</p>}
      {error && <p>{error}</p>}
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li> // Assuming `id` and `name` are properties of each item
        ))}
      </ul>
      {hasMore && !loading && (
        <button onClick={handleLoadMore}>Load More</button>
      )}
    </div>
  );
};

export default CursorPaginatedList;

Explanation:

  • Cursor: The cursor value is used to fetch the next set of data. The API typically returns a nextCursor that is passed as the cursor parameter in the next request.
  • Load More Button: Instead of using pagination controls, a “Load More” button is used to fetch the next set of data when the user reaches the end of the current list.
  • State Management: The cursor state is updated with the value of nextCursor returned by the API, and hasMore determines if more data is available.

4. Common Pagination Considerations

  • User Experience: Pagination should provide clear controls to help users navigate between pages, such as “Next”, “Previous”, and page numbers. A smooth loading state should be visible while data is being fetched.
  • Infinite Scrolling: If you want to enhance the user experience, you can implement infinite scrolling, where new pages of data are automatically loaded when the user scrolls to the bottom of the list.
  • Handling Large Datasets: When working with large datasets, ensure that pagination is handled both efficiently and transparently for the user. Consider using debouncing or throttling for API requests in case of frequent user interactions (e.g., button clicks or scrolling).
  • API Response Format: Ensure that the API response contains essential pagination information like the totalItems, totalPages, nextCursor, or hasMore to guide your pagination logic.

Leave a Reply

Your email address will not be published. Required fields are marked *