Improving UX and Performance with Hybrid Pagination
Adam C. |

Pagination is a critical aspect of any application that handles large datasets. It affects both the user experience and the performance of the app, especially when dealing with significant amounts of data.

Photo by George Pagan III on Unsplash

Typically, there are two main types of pagination:

  1. Server-side Pagination: The server sends a fixed number of records per page (e.g., 25 records per page), and every time the user changes the page, the client makes an API call to retrieve the next set of data. This method is efficient but leads to multiple network calls, which can slow down performance when users browse through many pages.
  2. Client-side Pagination: In this approach, the server sends all the data at once, and the frontend handles the pagination. While this reduces network calls, it can negatively impact performance when the dataset is large, as the initial load takes longer and uses a lot of memory.

The Hybrid Approach: Best of Both Worlds

To strike a balance between these two methods, we can adopt a hybrid pagination strategy. The idea is to:

  • Pull data in batches (e.g., 200 records at a time, covering multiple pages).
  • Use client-side pagination to handle the pages within the batch.
  • When the user navigates beyond the current batch (e.g., page 9 when batch size is 8 pages), the system makes another API call to fetch the next set of data.

This way, we reduce the number of network calls while keeping memory usage low and the initial load time quick.

How it Works

  • Server-side: Pull data in larger batches (e.g., 200 records covering 8 pages).
  • Client-side: Handle pagination within the current batch.
  • For random page requests: If the user requests a random page (e.g., page 31), calculate which batch this page falls into and pull the data accordingly.

Here’s an example code in React using this strategy:

const SS_PAGESIZE = 25;  // Items per page
const SS_BATCHSIZE = 8;  // Number of pages per batch (8 pages * 25 records)

const TimeTable = ({ initRecords, initOptions, season }) => {
  const [options, setOptions] = useState(initOptions);
  const [allRecords, setAllRecords] = useState(initRecords);
  const [activePage, setActivePage] = useState(1);
  const [batchStart, setBatchStart] = useState(0);
  const [isLoading, setIsLoading] = useState(false);

  const fetchRecords = async (batchStart) => {
    setIsLoading(true);
    try {
      const tResult = await getRankings(
        options.event,
        options.type,
        options.ageGroup,
        options.gender,
        season,
        batchStart,
        SS_PAGESIZE * SS_BATCHSIZE  // Fetch a batch of 200 records
      );
      setAllRecords(tResult);
    } catch (err) {
      console.error(err);
    }
    setIsLoading(false);
  };

  const handlePaginationChange = async (value) => {
    const batchStartNow = Math.floor((value - 1) / SS_BATCHSIZE) * SS_PAGESIZE * SS_BATCHSIZE;
    if (batchStartNow !== batchStart) {
      setBatchStart(batchStartNow);
      await fetchRecords(batchStartNow);  // Fetch new batch if needed
    }
    setActivePage(value);  // Update the page on the client-side
  };

  useEffect(() => {
    fetchRecords(batchStart);
  }, [batchStart, season, options]);

  const currentRecords = allRecords.data.slice(
    (activePage - 1) % SS_BATCHSIZE * SS_PAGESIZE,
    (activePage % SS_BATCHSIZE) * SS_PAGESIZE
  );

  return (
    <>
      <Table>
        <Thead>
          <Tr>
            <Th>Name</Th>
            <Th>Age</Th>
            <Th>Team</Th>
            {/* More headers... */}
          </Tr>
        </Thead>
        <Tbody>
          {currentRecords.map((record, idx) => (
            <Tr key={idx}>
              <Td>{record.name}</Td>
              <Td>{record.age}</Td>
              <Td>{record.team}</Td>
              {/* More data... */}
            </Tr>
          ))}
        </Tbody>
      </Table>

      <DNXPagination
        activePage={activePage}
        totalPages={Math.ceil(allRecords.total / SS_PAGESIZE)}
        onPageChange={handlePaginationChange}
      />
    </>
  );
};

Conclusion

By adopting a hybrid pagination approach, you can drastically reduce the number of network calls while improving the user experience. The app will load faster initially, and the server won't be overwhelmed with too many requests as the user navigates through the data.

This structure combines the theory behind hybrid pagination with the practical React implementation, making it a valuable resource for others interested in improving both performance and UX.