Server-side pagination in React-table
The react-table is a headless library to build highly customizable data tables in React. It takes care of the rendering logic once we provide the data and leaves the styling part to us. Along with that, it provides a number of utility and helper functions that help us to manipulate the data. This includes sort, filter, edit, search, and pagination. But, all this is done on the client-side, which means either the entire data is available for react-table to process or it can only use the data that is already available. For example, if you have 100 rows of data and want to display them in 10 pages with 10 rows per page, you will have to provide the entire 100 rows to the react-table initially so that it could split it for you. This is not the ideal behavior we might need.
Server-side pagination to the rescue
The logic for achieving server-side pagination in react-table is pretty simple: Just limit the data supplied to the react-table and control the limited data supplied. The implementation requires two steps:
- Make an API that supports pagination — It’s for the backend. So, let's consider it done. Going forward I’ll use a mock API for this purpose.
- Call the API whenever we need specific limited data — We’ll see how it can be achieved below.
Setting up
We’ll be using functional components for the view and hooks for state management. After bootstrapping the React project install react-table from npm. And that’s all we need.
We’ll need to build the following components:
- Table
- Pagination
- Loader
1. Table
The basic structure for the table is pretty much the same for all use cases. We’ll use the code available in the official docs here.
The columns and row data will be passed from the parent. We’ll use the CSS module for styling. Here I’ve just copied some basic table styles from W3schools.
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
Now, let's add loading and pagination prop to the table component. We can let react-table to know we’ll manage pagination ourselves by specifying it in the useTable
hook
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns: columnData,
data: rowData,
manualPagination: true
});
The final change will be adding a loading indicator to the table. This will make the user aware that data is being fetched. We’ll add the component later. But, now let's wrap up the table component by adding the indicator. The final file looks like this:
2. Pagination
The pagination component will be responsible for the following:
- Inform the parent which particular page is currently required.
- Show the user the active page.
- Enable the user to navigate to any of the available pages.
Let’s build the pagination component with these requirements
The next thing to do is to display the available page numbers. For this we’ll need either :
a) Total number of pages
b) Total number of rows/records and records per page count
In this example, we’ll use the second option. The total record count will most probably be available in the same API endpoint or in some cases in a separate one. It’s up to us to get that value supplied to the pagination component. The rows per page count can be user-defined or obtained from the API. Once we get these values, we can easily show the available page numbers in the component.
One more thing to add here is to highlight the current page number in the list of pages. This is done using conditionally adding a class to the page number buttons.
{pagesArr.map((num, index) => (
<button
onClick={() => onPageSelect(index + 1)}
className={`${styles.pageBtn} ${index + 1 === currentPage ?styles.activeBtn : ""}`}>
{index + 1}
</button>
))}
The next step is to use the pageChangeHandler callback to let the parent container know about the page change event so that it could fetch the specific data. This can be achieved using the useEffect hook.
useEffect(()=> {
pageChangeHandler(currentPage)
},[currentPage])
This simply calls the pageChangeHandler whenever the currentPage changes. Note that the API which I’m using expects the page number for jumping between pages. Some APIs expect a skip value for jumping between pages. The skip value is simply the rows/records to skip to get to the required page. This can be achieved in the following way:
useEffect(()=> {
const skipFactor = (currentPage - 1) * rowsPerPage;
pageChangeHandler(skipFactor);
},[currentPage])
The next step is to enhance the navigation a bit. We should not allow the user to click the next and previous page buttons on the last and first page respectively. For this, we’ll create two state variables that can disable those buttons in the required pages.
const [canGoBack, setCanGoBack] = useState(false);
const [canGoNext, setCanGoNext] = useState(true);
Next, we’ll use set these values based on the current page, using useEffect
useEffect(() => {
if (noOfPages === currentPage) {
setCanGoNext(false);
} else {
setCanGoNext(true);
}
if (currentPage === 1) {
setCanGoBack(false);
} else {
setCanGoBack(true);
}
}, [noOfPages, currentPage]);
In the end, we’ll make sure that the pagination appears only when there is more than one page. The final component looks like this:
3. Loader
Loader is simply a GIF that is displayed while the data is being fetched. You can use any loader of your choice. Here is the one which I made using one of the loader GIFs from icons8
import React from "react";
import loader from "../../assets/spinner.gif";
const Loader = () => <img src={loader} alt="Loader" />;
export default Loader;
The container
The container is where all our components will be combined together. Additionally, it will act as the single source of state for the table and pagination component. First, let's define the basic state.
const [pageData, setPageData] = useState({
rowData: [],
isLoading: false,
totalPages: 0,
totalPassengers: 0,
});
const [currentPage, setCurrentPage] = useState(1);
The API I used returns some random air travelers data, so the state variable names are that way.
Now let’s see how the container looks at this point
Next step we need to have some helper methods to fetch and format data for the table. Let's create a separate file for it.
Finally, we need to call the getData method whenever there’s a page change, which will return us the required data which we have to set in the state. The final container will look like this:
Note that I’ve limited totalPassengers to 150, as the API returns thousands of rows of data. In that case, we might want to alter the page number rendering logic to suit a large number of pages.
The whole code is available in my GitHub page. Additionally, I have displayed the first and last indices of the current page in the pagination component. You can try that too.
Thanks