React table is a library used for creating data tables with data grids which can sometimes be a hassle to make or deal with.
It uses hooks to create powerful tables which are lightweight and extensible, but is headless meaning that it does not have a design of its own, and we are free to design the table in whichever way we see fit. This was done to keep the library light weighted.
It offers us many features like Sorting, Global Filtering, Paginations, Column Filtering, Grouping, etc. You can have a look at all the features on its official site.
We can also make a table in React with the help of hooks only it will also be lightweight and easy to make, but sometimes it is better to use a library than to create something from scratch just to save a ton of time
In this blog, we will only be making a table with the help of React Table Library which can do sorting, filtering, and have pagination in it.
Let’s start…
Index
- Getting Started
- Creating our React App
- Installing the React table library and importing it
- Creating the Columns and displaying them
- Working on the UI part of the app
- Adding Different Functionalities to make the table more interactive
- Sorting
- Global Filtering
- Pagination
- Conclusion
Getting Started
We have prepared dummy data for this project consisting of 100 objects with key and value pairs and will be printing them dynamically from the Data.js file.
As React Table is a Headless library, we have to style it ourselves which will not be explained in detail here. But as we have only used Bootstrap for styling, you can use that as a starting point.
Before moving on to the development phase let’s see how our table will look after completion –
This is the live link to the table we will be building and its GitHub repository if you want to try something else.
Creating our React App
It’s easy to create a React App – go to the working directory in any IDE and enter the following command in the terminal:
npx create-react-app react-table-library
If you are unsure how to properly set up a create-react-app project you can refer to the official guide here at create-react-app-dev.
After the setup, run npm start in the same terminal to start the localhost:3000 where our React app will be hosted. We can also see all our changes there.
Installing React Table Library and Importing it
Like all other npm and yarn packages, installing or adding it is very simple
//For npm
npm install react-table
//For yarn
yarn add react-table
When the library is finally installed, it’s time to import it into our App.js file.
import React from "react";
import { dummy } from "./Data";
import { useTable } from "react-table";
useTable hook is the main hook of this library as it creates an instance that improves our interaction with the table.
According to the official documentation, “useTable is the primary hook used to build a React Table. It serves as the starting point for every option and every plugin hook that React Table supports.”
Creating and Displaying the Columns
useTable Hook has some options which enable it and us to interact with the table. The two most important options that we will be using here are data and columns.
const { instance } = useTable({
column,
data,
});
Where instance will be replaced with its different properties depending upon which property we chose to use.
Table Options
Column – This is a required field and should be memorized before passing it to the useTable hook.
This is the most important part of the UI as it will hold all the table headers and the columns inside it in an object form.
export const tableColumn = [
{
Header: "Id",
accessor: "id",
},
{
Header: "User Name",
accessor: "name",
},
{
Header: "Email",
accessor: "email",
},
{
Header: "Comments",
accessor: "body",
},
];
Data – It is also an important and required field that will contain our dummy data and should also be memorized.
Column Options
Header – It represents the header that we want each column to have.
Accessor – represents the key values of the dummy data that we made at the beginning of this tutorial. It can contain a string and a function.
The data returned from this will be by default sortable (but not without using the specific hook).
Every option that we pass to useTable Hook should be memorized as React Table relies on it to determine when state and side effects should update.
Working on the UI part of the app
We have our dummy data and column ready, now let’s work on the UI part of our table.
We will pass the memorized columns and data to useTable Hook. It will return the necessary props for the table, body, and transformed data which will be extracted to create the UI data.
import React, { useMemo } from "react";
import { dummy } from "./Data";
import { tableColumn } from "./MainTable/Columns";
import { useTable } from "react-table";
const App = () => {
const columns = useMemo(() => tableColumn, []); //memosized
const data = useMemo(() => dummy, []); //memosized
const {
//Instance Props
getTableProps, //table props from react-table
getTableBodyProps, //table body props from react-table
headerGroups, // array that contains all our headers
prepareRow, // Prepare the row (this function needs to be called for each row before getting the row props)
rows, //rows for the table based on the table passed
} = useTable({
columns,
data,
});
return (
<>
<table
{...getTableProps()}
className="table table-striped table-hover text-center fs-5 table-bordered border-dark col-12"
>
<thead>
{headerGroups.map((head) => {
return (
<>
<tr {...head.getHeaderGroupProps()}>
{head.headers.map((col) => {
return (
<>
<th
{...col.getHeaderProps()}
className="bg-secondary text-white fs-4"
>
{col.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>
</>
);
};
export default App;
Explanation of the above code
The above can be a tough opponent to beat, but this is only true until we understand what each line of the code has to say.
- We have wrapped our entire table inside the table tag in which we have passed the getTableProps() functions with a spread operator so that we can extract and use any props inside it in our app.
<table {...getTableProps()}>
...
</table>
Thead
- We have already been told that headerGroups is an array of all the headers of our app. Thus mapping through it will give us individual headers for our table.
- We again have to map an array of headers that were inside headerGroups to get the getHeaderProps function.
- By iterating over getHeaderProps(), we will get the value of our headers of the table.
<thead>
{headerGroups.map((head) => {
return (
<>
<tr {...head.getHeaderGroupProps()}>
{head.headers.map((col) => {
return (
<>
<th
{...col.getHeaderProps()}
className="bg-secondary text-white fs-4"
>
{col.render("Header")}
</th>
</>
);
})}
</tr>
</>
);
})}
</thead>;
TBody
- Similar to the table and thead tag, we will pass the prop getTableBodyProps() in tbody.
- We have extracted the value of rows from the instance which will be iterated using the map function to get the data of each field.
- For each row, we have to pass a prop – prepareRow() which will make the rendering of the row prop more efficient.
- Next, we return <tr> tags to render the row. In each <tr>, we again use map() to parse cells. For each cell, we create a td tag, pass in the prop resolver function getCellProps(), and then render the cell data using the render() method.
<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>;
The table will look like this after the custom styling
Adding Different Functionalities to make the table more interactive
Now, we have our table with a perfect UI of our choice and data up to 100 values but is this enough.
No, there are many things we as a developer can do, and this library has also a ton of features for us to play with. Let’s use some of the available features and make our table more interactive.
Sorting
One of the best ways to find the data of a client or a user from a table is to sort the whole table alphanumerically and find its name there if you don’t have the filtering option available which we will in the later section.
In the React Table Library, we have hooks for almost any operation that we want to perform in our table.
For sorting, we have useSortBy hook which we will be importing the right net to the useTable hook.
import { useTable, useSortBy } from "react-table";
And pass the hook as the useTable Hook argument.
const {
//Instance Props
getTableProps, //table props from react-table
getTableBodyProps, //table body props from react-table
headerGroups, // array that contains all our headers
prepareRow, // Prepare the row (this function needs to be called for each row before getting the row props)
rows, //rows for the table based on the table passed
} = useTable(
{
columns,
data,
},
useSortBy
);
If you want the sorting to be done by anything other than the default alphanumeric value, you’ll need to update your columns definition with a sort type field.
export const tableColumn = [
{
Header: "Id",
accessor: "id",
sort: "baisc"//sort type
},
{
Header: "User Name",
accessor: "name",
},
{
Header: "Email",
accessor: "email",
},
{
Header: "Comments",
accessor: "body",
},
];
There are three types of sorting that React Table Library allows us to have in our table –
- alphanumeric – Best for sorting letters and numbers (default)
- basic – Best for sorting numbers between 0 and 1
- datetime – Best for sorting by date
For this sorting functionality to work, we have to add a couple of things.
getSortByTogglrProps() – Like other props, we have discussed, it is also a required prop to enable column sorting by enabling the toggle feature of the sort between ascending and descending order (depending upon which sort type we have chosen, by default it is alphanumeric) when the user clicks the header of the respective row.
Span tag – this is basically for the UI part of our app as it will tell us when the sorting has started, and in which direction it is currently sorting.
Let’s add them in the header section of the table under th tag
<th
{...col.getHeaderProps(col.getSortByToggleProps())}
className="bg-secondary text-white fs-4"
>
{col.render("Header")}
<span>
{col.isSorted // true if the column is sorted at this moment
? col.isSortedDesc // for deciding the direction of the sorting
? " 🔽" // descending
: " 🔼" //ascending
: ""}
</span>
</th>;
We have another Column Option that we can use – disableSortBy. This option will disable the sorting abilities of a particular column in which it is used.
Filtering
Filtering is considered the most effective way to find any data in the table no matter the type of data.
React table provides two ways of filtering our table – Column Filtering and Global Filtering.
In this blog, we have only covered Global Filtering.
The process for using every hook from the library is very similar. First, we have to import the hook from the library, and then we have to pass that hook as the argument of the useTable hook.
import { useTable, useSortBy, useFilters } from "react-table";
But all the arguments should be in the same order as we want them in our table. Thus useGlobalFilter Hook will be before useSortBy hook
const {
//Instance Props
getTableProps, //table props from react-table
getTableBodyProps, //table body props from react-table
headerGroups, // array that contains all our headers
prepareRow, // Prepare the row (this function needs to be called for each row before getting the row props)
rows, //rows for the table based on the table passed
} = useTable(
{
columns,
data,
},
useGlobalFilter,
useSortBy
);
Now, let’s work on making this filter an active player in our app. But before that let’s pass some instances required for the global filter to work.
const {
//Instance Props
getTableProps, //table props from react-table
getTableBodyProps, //table body props from react-table
headerGroups, // array that contains all our headers
prepareRow, // Prepare the row (this function needs to be called for each row before getting the row props)
rows, //rows for the table based on the table passed
setGlobalFilter, //The function used to update the global filter value.
state: { globalFilter },// initialvalue located on the state object.
} = useTable(
{
columns,
data,
},
useGlobalFilter,
useSortBy
);
Now we are good to go and just have to apply these props in our UI
<span className="my-3">
<h3>Search</h3>
<input
className="col-2"
type="text"
value={globalFilter || ""}
onChange={(e) => setGlobalFilter(e.target.value)}
/>
</span>;
Pagination
At the very beginning of this blog, we said that we will be rendering 100 data in our table.
But these 100 pieces of data, if rendered at the same time will affect our DOM Tree and the efficiency of the app like speed or be more specific load time of our app will decrease.
There are four ways in which we can deal with this problem, and pagination is one of them. Let’s see how to make one using the React Table library.
As always we will first import the usePagination Hook from the library and then also extract some instance properties and table options to use for different features.
import {
useTable,
useSortBy,
usePagination,
useGlobalFilter,
} from "react-table";
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
setGlobalFilter, // for global filter
// rows replaced with page for pagination
page, //An array of rows for the current page, determined by the pageIndex value.
nextPage,
previousPage,
canPreviousPage, // a boolean value which is true if the pageIndex is not 0
canNextPage, //a boolean value which is true if the pageIndex is not the last page
pageOptions,
gotoPage, // gives us the ability to jump to any page
state: {
pageIndex, // Current PageIndex Value
globalFilter, // current global filter value
},
} = useTable(
{
columns,
data,
initialState: {
pageIndex: 0, // Default value 0
pageSize: 5, //Determines the amount of rows on any given page. Default Value 10
},
},
useGlobalFilter,
useSortBy,
usePagination //should be at the very bottom to avoid errors
);
You might have already noticed that we have replaced rows with page, and now the page will be rendered and mapped through like the rows when we were building our UI in the first section.
But, there is one catch here, this page prop does not have all the rows in it as the rows prop did.
It only has 5 rows in it at a time which is equal to the value of pageSize we have given in the above code which is 5.
Thus we can say that the number of rows rendered through the page will be equivalent to the value of pageSize.
UI for Pagination
<div className="d-flex justify-content-between my-3">
<button
className="px-3 py-1 mx-3 text-center btn-secondary"
onClick={previousPage}
disabled={!canPreviousPage} //pages before 1 are disabled
>
Previous
</button>
<span className="fs-4 text-center">
Page
<strong className="mx-3">
{pageIndex + 1} of {pageOptions.length}
</strong>
| Go To Page
<input
type="number"
className="col-1 text-center"
defaultValue={pageIndex + 1}
onChange={(e) => {
const pageNumber = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(pageNumber);
}}
/>
</span>
<button
className="px-3 py-1 mx-3 text-center btn-secondary"
onClick={nextPage}
disabled={!canNextPage} //pages after 50 are disabled
>
Next
</button>
</div>;
In our UI of the Pagination Component, we have a button for the next page and previous page which are disabled if they have reached their max limit which is 0, and the last page using their respective props.
A span tag will show us the current page we are on and the last page of the app. And an input field through which we can go to any page number we want simply by using gotoPage prop. It will look something like this at the end.
Conclusion
In this blog, we have seen how to make a table in React with the help of a library – React Table Library which can do things like filtering, sorting, and pagination.
You can visit the library to know more about it and try all of its features. You will be amazed to know how much a table built in React with this library can do.