Here's a quick summary of everything we released in Q1 2024.

React Table: A Complete Guide

This guide will teach us how to efficiently use the react-table library to create tables in our React application. We will learn how to fetch these data from Hygraph and implement features like sorting, filtering, and lots more.
Joel Olawanle

Joel Olawanle

Sep 07, 2022
React Table

A table is an arrangement that organizes any information into rows and columns—it stores and displays data in a structured and clear format that everyone can understand. For example, when you want to display dashboard statistics, a list of details such as staff details, products, students, and lots more.

Implementing tables in React can get cumbersome and technical, especially when you need to perform basic table functionalities like sorting, filtering, pagination, and lots more.

#What is React Table?

React table is a free open-source, headless UI library with about 19,000 stars on GitHub as of when this article was written. This library receives frequent updates on the daily basics. Its collection of hooks is easily used for building tables and data grid experiences. It is simple to set up and customize. It is also very flexible to use.

In March 2020, Tanner Linsley, the brain behind React table, released React Table v7, which he referred to as "the culmination of over a years [sic] worth of work to refactor the entire library to a hooks-only UI/Style/Markup agnostic table building utility."

At React Summit, 2022, where he spoke on five years of building React tables, he mentioned that v8 was going to be released and this time around, bringing in a lot of features using typescript. He also mentioned that the library was now available to frameworks like Vue, solid, and Svelte with the name TanStack Table.

React table uses hooks to help build tables, giving you complete control over how you want the UI to appear and function (Headless UI). These hooks are lightweight, fast, fully customizable, and flexible but do not render any markup or styles for you.

React table is used by many well-known tech companies, such as Google, Apple, and Microsoft, due to its flexibility and easy-to-implement features. These features, such as sorting, pagination, and filtering, are implemented with hooks.

Some Use cases of React Tables

We can use react-tables for basic features such as sorting, filtering (global and column), pagination, and lots more.

These tables could be a hospital list of drugs to display drugs and their prices/information, staff details, student details, sports leaderboards/statistics, and lots more.

When is best to use React Table

Many conditions must be put in place when thinking of a React table library or when building your table from scratch. Some of these conditions include:

  • Pricing: The cost of such package as not all packages/libraries are free to use.
  • Features: The features they implement.
  • Flexibility: The ease and ability to customize our tables.
  • And more.

General advice regarding using the React table library is to use React table when you need a simple table with small data and basic features like sorting, global filtering, column filtering, and pagination. Also, when you want complete control over the table's UI design and styling.

When is best not to use React table

Having understood the fundamental reasons why you should use React-table, it is also expedient to mention some scenarios when you should not use React tables. Some of these scenarios include:

  • When working with extensive data: As we said earlier, react-tables work with medium-sized data as, in terms of performance, they can't handle an extensive list of data with long tables like when we use sheets.
  • When implementing inline editing of columns: React table is used for rendering simple tables. This doesn't include mutating table data as most of these data are collected from external sources, which becomes difficult to handle inline editing. This doesn't mean it's impossible, as we can build our custom hook and plugin to handle it.
  • The last scenario is when you need an automatic solution to handle horizontal and vertical scroll for both touch and non-touch devices. React table is a headless UI, meaning it handles the functionality while we handle the UI design, meaning we are in charge of how the scroll works.

#React table features

React table has some fantastic features that make it easy to build tables. These features include:

  • It is lightweight, about 5kb - 14kb+.
  • It works with hooks.
  • It is fully customizable (JSX, templates, state, styles, callbacks).
  • Does not influence data calls (it does not perform data calls; it just uses passed-in data).
  • It performs filtering (global and column).
  • It performs sorting for each column based on data types (numbers, string, boolean, select input, etc.).
  • It supports pagination for long tables.
  • It supports nested/grouped headers.

And a lot more.

#Cons and challenges of react-table

Even though react table sounds like a go-to for all forms of tables you want to implement. These are some challenges that will make you reconsider your choice. Some of these challenges include:

  • Responsiveness: To make a table responsive, you must change its layout, so it suits smaller screen sizes. This has to be handled by you because React table is a headless UI that only handles the functionalities. Therefore, displaying tables on different devices becomes cumbersome and complicated. This can result in having to search for alternatives that handle such.

  • Scrolling: When working with tables with extensive data, which could amount to more columns, we would need to scroll in both directions scrolling then becomes something to worry about. The default browser scroll bar will only work well for a full-width table, which most tables are not. Implementing custom scrollbars within our tables can become tricky to implement on touch and non-touch screens. This could make us result in other already made libraries that handle such.

  • Managing column width: Handling the width of columns based on their data can be tricky to handle alongside many other stylings. Every time the data changes, our table's layout is altered and the column width changes, which causes further issues. Handling issues like this could need us to get another library.

#Should you build your table, and when?

There are many reasons, but generally, a significant reason as to when you should build your table and not go ahead to install an extra library is when you just need a table to display information without interactions such as sorting and filtering.

Another reason is when you want to handle the table's UI properly. This includes its responsiveness, scrolling, column width, and lots more.

#Building React tables with the react-table library

This guide will build a table with react-table library and implement basic functionalities such as sorting and filtering. We will fetch our data from Hygraph via GraphQL. On our Hygraph project, we created a model and populated it with mock list of employees.

Hygraph is a headless content management system that enables teams to distribute content to any channel. If this is your first time learning about Hygraph, establish a free-forever developer account.

We will use Hygraph to store all the employee details, so we can later pull them into our React application. In our model, we would add fields based on the mock list of employees. Hygraph supports all forms of field types such as single line, multi-line, Rich Text field and lots more.

Pulling data from Hygraph

In a situation where you have successfully added all your data to hygraph, or you have your content API ready, we can now fetch these data into our application and store it in a state.

To fetch our data from Hygraph, we use GraphQL using the graphql-request library. We can install this library using the command below in our project directory:

npm i graphql-request

We can confirm if we successfully installed this package in our package.json file's dependencies object:

"dependencies": {
// rest of the dependencies installed
"graphql-request": "^4.3.0",
},

Once that is confirmed, we can navigate to whichever component we want to create our table. We would use the useState() and useEffect() hooks to fetch our data. We will use the useState() to store and hold our data/content, while the useEffect() hook will help make the fetch request once our application renders:

import { useState, useEffect } from 'react';
import request from 'graphql-request';
const App = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const { datasets } = await request(
'https://api-us-east-1.hygraph.com/v2/cl6o3qnz40n6401uje6140m78/master',
`
{
datasets (first:50) {
id_number
firstName
lastName
email
department
dateJoined
}
}
`
);
setData(datasets);
};
fetchData();
}, []);
return (
<div className="container">
{/* ... */}
</div>
);
};
export default App;

Installing react table

For us to get started with react-table, we would have to install it in our react project directory using the command below:

npm i react-table

Once that is done, we can confirm that the package has successfully installed in our package.json file's dependencies object:

"dependencies": {
// rest of the dependencies installed
"react-table": "^7.8.0",
},

Once that is done, we can create our table in our React application. The first step would be to define our columns.

Defining table's columns

When using React tables, data and columns are the two significant pieces of information needed. The information we will pass into the rows is known as the data, and the objects used to define the table columns are called the columns (headers, rows, how we will show the row, etc.).

For this guide, we will create our columns in a separate JavaScript file and then import it into our App.js file, where we will create our table.

// columns.js
export const COLUMNS = [
{
Header: 'ID',
accessor: 'id_number',
},
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
{
Header: 'Email',
accessor: 'email',
},
{
Header: 'Department',
accessor: 'department',
},
{
Header: 'Date Joined',
accessor: 'dateJoined',
},
];

The columns array consists of as many objects as we want, and these objects represent single columns in our table. These objects consist of two items which are:

  • Header – this is the column's name that will be displayed to help identify each column.
  • Accessor – this is the key in our data. This is what will be used to assign each value to a column.

The column object can contain more information we will explore as we proceed in this guide.

Creating table instance with the useTable hook

React table makes use of hooks to create our table. The first hook we will use is the useTable hook to instantiate our table so we can access all the methods needed to create our table.

We do this by passing in the data and column as an argument. We would first use the useMemo hook to help memorize our column's value so it doesn't get called on every render.

// App.js
import { useState, useEffect, useMemo } from 'react';
import { useTable } from 'react-table';
import { COLUMNS } from './components/columns';
const App = () => {
const [data, setData] = useState([]);
// useeffect ...
const columns = useMemo(() => COLUMNS, []);
const tableInstance = useTable({ columns, data });
return (
<div className="container">
{/* ... */}
</div>
);
};
export default App;

From the tableInstance, the hook will return the necessary props for the table, body, and transformed data to create the header and cells for our table. Let's destructure directly to access these props:

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

All these hooks and array methods will be passed into our form markup to help create our table.

Creating our table

For any table, we will have HTML semantics such as table , th, tbody , tr and td. These hooks and array methods will be passed into these HTML table semantics to help get each data for our rows and columns appropriately:

// App.js
const App = () => {
// ...
return (
<div className="container">
<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>
</div>
);
};
export default App;

At this point, when we check our browser, our table will look like this:

table with data

Styling our table

At this point, our table looks weird. We will add some basic styling for this guide to make our form friendly and attractive. We can use the HTML tags to add styling directly, but if you have more than one form in which you want to apply specific styling, you will need to add classes:

/* index.css */
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;
}

Our table will now look like this:

styled table with data

#Implmenting React table filter/search functionality

There are two basic ways we would want to implement filtering in tables. We can either want to implement global filtering or column filtering. Let's learn how to implement each of them.

How to implement Global Filtering with useGlobalFilter hook

The useGlobalFilter allows us to filter the entire table based on any value passed into our filter text field.

Let's start by passing in the useGlobalFilter hook as a second parameter where we initialized the useTable hook. And we will also add two methods to the previously destructured hooks and array methods:

const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
state,
setGlobalFilter,
prepareRow,
} = useTable({ columns, data }, useGlobalFilter);

We added the state and setGlobalFilter methods. The next step would be to destructure globalFilter from the state method:

const { globalFilter } = state;

Finally, we would create a text field which we would use to enter the text that we would use to filter each cell in our table:

<div className="search-container">
<input
type="text"
value={globalFilter || ''}
onChange={(e) => setGlobalFilter(e.target.value)}
/>
</div>

In the input field, we set the value to globalFilter and the method to help set this value is setGlobalFilter(). With this, we have been able to implement global filtering successfully. Our entire code now looks like this:

// App.js
import { useState, useEffect, useMemo } from 'react';
import { useTable, useGlobalFilter } from 'react-table';
import request from 'graphql-request';
import { COLUMNS } from './components/columns';
const App = () => {
const [data, setData] = useState([]);
// useEffect ...
const columns = useMemo(() => COLUMNS, []);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
state,
setGlobalFilter,
prepareRow,
} = useTable({ columns, data }, useGlobalFilter);
const { globalFilter } = state;
return (
<div>
<div className="search-container">
<input
type="text"
value={globalFilter || ''}
onChange={(e) => setGlobalFilter(e.target.value)}
/>
</div>
<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>
</div>
);
};
export default App;

How to implement column filtering with useFilters hook

Column filtering refers to having filter fields on each column capable of filtering the table based on each. We can implement column filtering with the useFilters hook. These fields will be added directly to the table header, meaning we would need to edit the columns and markup.

Let's start by creating the text field in a separate component which we would later add to all columns:

// FilterForm.js
import React from 'react';
const FilterForm = ({ column }) => {
const { filterValue, setFilter } = column;
return (
<span>
<input
value={filterValue || ''}
onChange={(e) => setFilter(e.target.value)}
/>
</span>
);
};
export default FilterForm;

This form takes in a column prop which we can destructure to get the filterValue and setFilter values. These values would be used to control the forms input to help filter each column.

The next step would be to import this form into our columns file. We will also add this form with the filter property to each column, as seen below:

// columns.js
import FilterForm from './FilterForm';
export const COLUMNS = [
{
Header: 'ID',
accessor: 'id_number',
Filter: FilterForm,
},
{
Header: 'First Name',
accessor: 'firstName',
Filter: FilterForm,
},
{
Header: 'Last Name',
accessor: 'lastName',
Filter: FilterForm,
},
{
Header: 'Email',
accessor: 'email',
Filter: FilterForm,
},
{
Header: 'Department',
accessor: 'department',
Filter: FilterForm,
},
{
Header: 'Date Joined',
accessor: 'dateJoined',
Filter: FilterForm,
},
];

Finally, we would need to import the useFilters hooks in our table component, and add it to the useTable hook instance:

// App.js
import { useState, useEffect, useMemo } from 'react';
import { useTable, useFilters } from 'react-table';
// opther imports
const App = () => {
const [data, setData] = useState([]);
// useEffect ...
const columns = useMemo(() => COLUMNS, []);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data }, useFilters);
return (
<div>
{/* ... */}
</div>
);
};
export default App;

Finally, in our markup, we would now add the filter field in our table header:

// App.js
const App = () => {
// ...
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
<div>
{column.canFilter ? column.render('Filter') : null}
</div>
</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>
</div>
);
};
export default App;

Note: If you want to implement both global and column filtering, ensure that when calling the hooks in the useTable instance ensure that useFilters comes before useGlobalFilter, as seen below:

const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
state,
setGlobalFilter,
prepareRow,
} = useTable({ columns, data }, useFilters, useGlobalFilter);
const { globalFilter } = state;

#Implementing sorting in react table with useSortBy hook

When you create tables, we might want to add a sorting feature whereby when the user clicks the column header, the entire table sorts in ascending or descending order depending on the data type.

We can implement sorting with the useSortBy hooks from react-table. As usual, we would need to add this hook to the useTable instance:

// App.js
import { useState, useEffect, useMemo } from 'react';
import { useTable, useSortBy } from 'react-table';
// other imports
const App = () => {
const [data, setData] = useState([]);
// useEffect ...
const columns = useMemo(() => COLUMNS, []);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable({ columns, data }, useSortBy);
return (
<div>
{/* ... */}
</div>
);
};
export default App;

The next step would be to add a span tag alongside a condition to display whichever direction the table is sorted based on that column:

// App.js
const App = () => {
// ...
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
<span>
{column.isSorted
? Column.isSortedDesc
?' 🔽'
: '🔼'
:"}
</span>
</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>
</div>
);
};
export default App;

Once this is done, our application will now handle sorting by clicking on each header item.

#Conclusion

In this guide, we have learned what react table is and how and when to use react tables in our React project. You can play around with the source code and check out the React table docs examples for some React Table examples.

Hygraph is a powerful headless content management system and can also help us perform localization and internalization, which we can use in our React application.

Blog Author

Joel Olawanle

Joel Olawanle

Joel Olawanle is a Frontend Engineer and Technical writer based in Nigeria who is interested in making the web accessible to everyone by always looking for ways to give back to the tech community. He has a love for community building and open source.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.