Jun 1, 2021· 11 mins to read

Implementing React table pagination handling one million records


Implementing React table pagination handling one million records

Have you ever faces a situation where you react application stuck with a loading state or slow when the data starts to grow?. Well, Most of us would have faced this scenario in our work. Here, we will see how to solve it using react table pagination on the server-side to handle one million records.

Since we will use server-side pagination, we need to set up a nodejs server to fetch the data from MongoDB. checkout this article to set up a nodejs server with pagination, or you can create a mock API with data (1 million records)

React Table

React table is a lightweight and extensible data table for react. It’s a headless UI for data tables in react applications. If you’re new to react table, I will suggest getting started with basics and try pagination.

Here’s a simple react table component without pagination

export const BasicTable = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    footerGroups,
    rows,
    prepareRow,
  } = useTable({
    columns,
    data,
  });

  return (
    <>
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <th {...column.getHeaderProps()}>{column.render("Header")}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map((row) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </>
  );
};

useTable hooks provide all the properties and methods for the table. So, for example, we need to map getTableProps props to the table jsx element and take care of all the functionalities.

As you can see, that table is scrollable because of rendering the whole data in the table. To make it a better experience for the users, we can implement pagination.

Pagination

To use pagination in the table, we need usePagination hooks from react table. import the hook and add it to the useTable

import { useTable, usePagination } from "react-table";
const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  footerGroups,
  rows,
  prepareRow,
} = useTable(
  {
    columns,
    data,
  },
  usePagination
);

Once we have pagination hooks, replace the rows prop with page prop inside the useTable hooks because every data inside the table will be based on the page hereafter. So, we need to render pages instead of rows.

export const BasicTable = ({ columns, data }) => {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    footerGroups,
    page,
    prepareRow,
  } = useTable(
    {
      columns,
      data,
    },
    usePagination
  );

  return (
    <>
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <th {...column.getHeaderProps()}>{column.render("Header")}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {page.map((row) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </>
  );
};

Once we make the change, our table will render only the first 10 elements from the data. So, it’s time to implement the pagination buttons in the footer to navigate or fetch the next set of data.

To implement navigation for the paginated table, react table provides four properties. They are,

  • nextPage
  • previousPage
  • canPreviousPage
  • canNextPage

nextPage is an action-based prop that we can use inside the onClick method. canNextPage returns a boolean to add a condition whether to render the next button or not. For example, if we reach the last page. We can disable the next button based on the canNextPage prop.

Also usePagination provides state that returns pageSize and pageIndex . we can use it to show the number of items on the page and also for server-side pagination. We will come back to that in the latter part of the article.

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  prepareRow,
  page,
  state: { pageIndex, pageSize },
} = useTable(
  {
    columns,
    data,
  },
  usePagination
);

Here’s the Paginated Table with all the basic functionalities,

React Table Pagination

Everything is great on the in-built pagination of react table. But, there are use-cases where we need to have more control over the component. One of the use-cases is Server-side pagination.

In the above example, we send the whole data to react table component. What if we get the data from the paginated API. We will get the first 20 rows from API, and we need to call API to fetch the next row of data.

How does react table handle this situation? we are discussing it here because this is the common scenario we will face in real product development(not just simple pagination). First, let’s see how to handle this situation in an efficient way.

Here’s the flow for server side pagination in react table,

  • Change to Manual Pagination in react table
  • Call API whenever pageIndex changes

Now, add this property inside useTable

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  prepareRow,
  page,
  canPreviousPage,
  canNextPage,
  pageOptions,
  pageCount,
  gotoPage,
  nextPage,
  previousPage,
  setPageSize,
  setHiddenColumns,
  state: { pageIndex, pageSize },
} = useTable(
  {
    columns,
    data,
    manualPagination: true,
    pageCount: controlledPageCount,
  },
  usePagination
);

After that add an useEffect hook, so whenever the pageIndex changes we can fetch the data

React.useEffect(() => {
  fetchData && fetchData({ pageIndex, pageSize });
}, [fetchData, pageIndex, pageSize]);

fetchData function can be a prop from the parent component or the same component. In most cases, we will be abstracting this Table component. So, it will be a prop from the parent component.

Here’s the fetchData function takes the current element and check if it’s the same as the previous. If not, it will fetch the new data.

const fetchData = useCallback(
  ({ pageSize, pageIndex }) => {
    // console.log("fetchData is being called")
    // This will get called when the table needs new data
    // You could fetch your data from literally anywhere,
    // even a server. But for this example, we'll just fake it.
    // Give this fetch an ID
    const fetchId = ++fetchIdRef.current;
    setLoading(true);
    if (fetchId === fetchIdRef.current) {
      fetchAPIData({
        limit: pageSize,
        skip: pageSize * pageIndex,
        search: searchTerm,
      });
    }
  },
  [searchTerm]
);

Since we have the fetchData function in the parent component, we use the useCallback hook that helps us to avoid unnecessary re-render. We also pass the searchTerm for filtering functionality.

fetchAPIData function fetches the data and set it in state value

const fetchAPIData = async ({ limit, skip, search }) => {
  try {
    setLoading(true);
    const response = await fetch(
      `/companies?limit=${limit}&skip=${skip}&search=${search}`
    );
    const data = await response.json();

    setData(data.data);

    setPageCount(data.paging.pages);
    setLoading(false);
  } catch (e) {
    console.log("Error while fetching", e);
    // setLoading(false)
  }
};

complete source is available here

Copyright © Cloudnweb. All rights reserved.