Building a React Table Component with react-table
Tags: react, javascript
Published: 2020-08-18 19:06:02

If you’re building internal tools – admin panels, dashboards, CRMs, you name it – chances are you’re thinking about how to build a table component to display, edit, and manipulate data. And if you’re working in React, you (thankfully) don’t need to build it from scratch: the [react-table](https://github.com/tannerlinsley/react-table) library gives you Hooks to get tables up and running quickly.  

By the end of this tutorial, you’ll know how to:
* **Build a simple table with React** and modify data, columns, and headers
* **Give your table a custom UI** by passing CSS into each component using `styled-components` or piggybacking off a React component library.
* **Extend your table with more features** like sorting, filtering, and pagination.

Learning all of these things yourself can be complicated. Thankfully, the react-table library is killer (and we made you this guide to help).

## Intro: react-table
<br>

React-table is an open-source library specifically for building (you guessed it) tables in React. [The library has over 11.5k stars on GitHub](https://github.com/tannerlinsley/react-table) and is used by tons of big-name tech companies, like Google, Apple, and Microsoft. Plus, we like it so much here at Retool that we sponsor it.

We like react-table because it’s easy to set up, customize, and extend. The library covers the basics of a useful table — sorting, filtering, and pagination — but also goes much deeper with advanced features like:

* Grouping
* Expanded State
* Custom Plugin Hooks

It should be noted that react-table is a “[headless](https://www.merrickchristensen.com/articles/headless-user-interface-components/)” UI library. The library doesn’t actually render a user interface. While this may sound strange, it was designed this way to give you more control over the look and feel of the react-table component while keeping the package size small. Don’t worry, adding UI is easy, and we will cover that later on. 

Since we’re all about internal tools, let’s imagine that we are building a table to display order information for customer service reps. Our table will need to display customer info (name and address) and order info (order number and date) for each customer’s purchase.

When you’ve finished working through this tutorial, you will have five versions of a table built with react-table: simple, styled, sortable, filterable, and paged. Below is the final, paged version we’re aiming for.

![Retool-React-Table-Pagination](https://retool.com/blog/content/images/2020/07/Retool-React-Table-Pagination.gif)

It could look better, but that’s what CSS is for!

## Build a simple table with react-table
<br>

First, we’ll build a basic table to display data, no styling or extra features. Our customer support reps need an easy way to view order information for each customer. Our simple table will have two top-level headers: **User Info** and **Order Info**. Under **User Info**, we need two secondary headers to display each customer’s **Name** and **Address**. Under Order Info, we need two more secondary headers to display the **Date** that the order was made and the **Order Number**.

In this section, we’ll build a table with four columns split into two groups. We’ll break down how to define the shape of column objects and data, parse header groups, and fill out our rows and cells. At the end, expect to see something like this:

![Retool-React-Table](https://retool.com/blog/content/images/2020/07/Retool-React-Table.png)
*Note that we did add a tiny bit of extra styling to this section, so the table has lines.*

Taking care of housekeeping basics first, you’ll need to install react-table by using a package manager (Yarn or npm) and import the library into your React app:

```
import { useTable } from 'react-table';
``` 

Then, once react-table is installed and imported, it’s time to define our data and columns by way of the `useTable` Hook. React-table leverages [Hooks](https://reactjs.org/docs/hooks-overview.html), which are a fairly new addition to React (as of version 16.8). If you’re not familiar with React Hooks, we recommend taking a look at React’s [Hooks at a Glance](https://reactjs.org/docs/hooks-overview.html) documentation.

The most important Hook for our table is `useTable`. We’ll pass two arguments to `useTable`:

1. **data** = table data defined with the `useMemo` Hook (data must be [memo-ized](https://reactjs.org/docs/hooks-reference.html#usememo) before it can be passed to `useTable` to cut down on calculation time by preventing unchanged data from being rerun)

```
const data = React.useMemo(() =>
 [
 {
 name: 'Kim Parrish',
 address: '4420 Valley Street, Garnerville, NY 10923',
 date: '07/11/2020',
 order: '87349585892118',
 },
 {
 name: 'Michele Castillo',
 address: '637 Kyle Street, Fullerton, NE 68638',
 date: '07/11/2020',
 order: '58418278790810',
 },
 {
 name: 'Eric Ferris',
 address: '906 Hart Country Lane, Toccoa, GA 30577',
 date: '07/10/2020',
 order: '81534454080477',
 },
 {
 name: 'Gloria Noble',
 address: '2403 Edgewood Avenue, Fresno, CA 93721',
 date: '07/09/2020',
 order: '20452221703743',
 },
 {
 name: 'Darren Daniels',
 address: '882 Hide A Way Road, Anaktuvuk Pass, AK 99721',
 date: '07/07/2020',
 order: '22906126785176',
 },
 {
 name: 'Ted McDonald',
 address: '796 Bryan Avenue, Minneapolis, MN 55406',
 date: '07/07/2020',
 order: '87574505851064',
 },
 ],
 []
)
```

2. **columns** = column definitions defined with the `useMemo` Hook (column defs must be memoized before they can be passed to `useTable`)

```
const columns = React.useMemo(
 () => [
 {
 Header: 'User Info',
 columns: [
 {
 Header: 'Name',
 accessor: 'name',
 },
 {
 Header: 'Address',
 accessor: 'address',
 },
 ],
 },
 {
 Header: 'Order Info',
 columns: [
 {
 Header: 'Date',
 accessor: 'date',
 },
 {
 Header: 'Order #',
 accessor: 'order',
 },
 ],
 },
 ],
 []
)
```

Take a second to look at the relationship between **data** and **columns**. The `accessor` in **columns** is the “key” in the **data** object. This is important to be able to access the right data for each column once we use `useTable`.

Once we have defined **data** and **columns**, it’s time to implement our `useTable` Hook. Pass **data** and **columns** into `useTable`, which will return properties that we can extract to build our table UI.

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

Now, we’ll use these extracted properties to build out our table via familiar JSX tags –  `<table>`, `<thead>`, `<tr>`, `<th>`, and `<tbody>` – and then fill in our properties from `useTable`.

### Table

First, we need `<table>` to wrap the rest of our code, and we need to pass the `getTableProps()` function in to resolve any table props.

```
<table {...getTableProps()}>
 ...
</table>
```

### Headers

Things start to heat up a little bit when we start to build our headers! On a high level, all we are doing is creating our header rows using the column header names that we defined above. Before we jump into the code, let’s look at the rendered table to get a better idea:

![Retool_headers_groups](https://retool.com/blog/content/images/2020/07/Retool_headers_groups.png)

Each circled section in the table above is a **headerGroup**, which is simply an object that contains an array of **headers** for that row. For this table, we will have two header rows: the header circled in red is the first headerGroup, and the header circled in blue is the second headerGroup.

To get the data we need to build these headers out of headerGroups, we will be using JavaScript’s `map()` method. If you’re unfamiliar with it, take a second to read the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).

First, we have our `<thead>` tag, which is simple enough. Inside of that tag, we are going to use `map()` to parse each headerGroup, creating a new row using the `<tr>` and passing that headerGroup’s `getHeaderGroupProps()` method in.

```
{headerGroups.map(headerGroup => (
   <tr {...headerGroup.getHeaderGroupProps()}>
     ...
   </tr>
))}
```

Inside of the `<tr>`, we use `map()` again, but this time on the array of headers. Each header object has a `Header` property (which is the name you’ll give each header), a `render()` function, and another prop resolver function called `getHeaderProps()`. 

For each column, we use the `<th>` tag to create the column, being sure to pass that column’s prop resolver function `getHeaderProps()` and then use the `render()` function to access the Header.

```
<thead>
 {headerGroups.map(headerGroup => (
   <tr {...headerGroup.getHeaderGroupProps()}>
     {headerGroup.headers.map(column => (
       <th {...column.getHeaderProps()}>{column.render('Header')}</th>
     ))}
   </tr>
 ))}
</thead>
```

### Table Body

Similar to how we did `<table>` and `<thead>`, we add `<tbody>` and pass the prop resolver function `getTableBodyProps()` in. Then, we use `map()` to iterate through **rows**, which is an array of **Row** objects. Each **Row** object has a **cells** field, which is just an array of **Cell** objects that contain the data for each cell in the row.

![Retool-React-Table-Component-Table-Body](https://retool.com/blog/content/images/2020/07/Retool-React-Table-Component-Table-Body.png)

The orange circle shows a row, and the pink circle shows a cell.

For each row, we need to pass the row object to the `prepareRow()` function, which helps with rendering efficiently. 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()}>
 {rows.map(row => {
   prepareRow(row)
   return (
     <tr {...row.getRowProps()}>
       {row.cells.map(cell => {
         return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
       })}
     </tr>
   )
 })}
</tbody>
```

Let’s put all of that together to render our table.

```
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>
)
```

Using the above code, you’ll end up with a rendered table that looks like this:

![Retool-Simple-React-Table-Final](https://retool.com/blog/content/images/2020/07/Retool-Simple-React-Table-Final.png)

## Give your table a custom UI
<br>

Even if you’re building a tool that will only be used by an internal team, it’s still important for the UI to look good (or at least not terrible). Styling (at least the basics) is extra important with react-table too since no components are actually rendered as part of the library. Without any styling, you’ll end up with a table like this:

![Retool-Simple-Unstyled-React-Table](https://retool.com/blog/content/images/2020/07/Retool-Simple-Unstyled-React-Table.png)

You can style your react-table component by either creating custom styles or through a React component library. The final product from this section will look like this:

![Retool-React-Table-Styled](https://retool.com/blog/content/images/2020/07/Retool-React-Table-Styled.png)

Isn’t it beautiful?

### Using the `style` Prop
Styling your table with react-table is as simple as passing CSS into the `style` prop of each component. Let’s look at the `<th>` tag for styling a header row:

```
<th
 {...column.getHeaderProps()}
 style={{
   borderBottom: 'solid 3px blue',
   background: 'green',
   color: 'white',
   fontWeight: 'bold',
 }}
>
```

You can also use CSS files and CSS modules if you’d like. Check out React’s [CSS docs](https://www.w3schools.com/react/react_css.asp) for more info.

### Using `styled-components`
`styled-components` is a [nifty React library](https://blog.logrocket.com/8-reasons-to-use-styled-components-cf3788f0bb4d/) that lets you style React components with CSS directly inside of JS code (as opposed to external CSS files). Lately, it’s become a really popular way to handle component styling in React, so you might want to use it for your table.

To use `styled-components`, [install the library](https://github.com/styled-components/styled-components) and import it into your project. Create a component `Styles` that uses `styled` from the `styled-components` library to create a `div` with the styles for your table in CSS. Move all of your code for creating the `Table` component into its own function. Then, in your `App` function (where your columns and data are defined), return `<Styles>` with your `<Table>` rendered inside. This will apply the styles from the `styled-components` on to your table.

```
import styled from 'styled-components'

/* Pssst this has the rest of the styling we've been using
to give our table borders and non-ugly spacing */

const Styles = styled.div`
 table {
   border-spacing: 0;
   border: 1px solid black;

   tr {
     :last-child {
       td {
         border-bottom: 0;
       }
     }
   }

   th,
   td {
     padding: 0.5rem;
     border-bottom: 1px solid black;
     border-right: 1px solid black;

     :last-child {
       border-right: 0;
     }
   }
  
   th {
     background: green;
     border-bottom: 3px solid blue;
     color: white;
     fontWeight: bold;
   }
 }
`

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

 // Render the UI for your table
 return (
   <table {...getTableProps()} >
     ...
   </table>
 )
}

function App() {
 const columns = React.useMemo(...)

 const data = React.useMemo(...)

 return (
   <Styles>
     <Table columns={columns} data={data} />
   </Styles>
 )
}

export default App
```

### Using a React Component Library
If you don’t want to style things yourself, using a React component library is the way to go. For this example, we’re going to use the `material-ui` library to create a nice table with react-table.

Going off of the Cell styling example above, we simply have to import `TableCell` from `@material-ui/core/TableCell`.

```
import TableCell from '@material-ui/core/TableCell'
...
<TableCell {...cell.getCellProps()}>
   {cell.render('Cell')}
</TableCell>
```

This will bring all of the styling of the `TableCell` component in `material-ui`. No extra work for you!

Click [here](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/material-UI-components) for a full sandbox version of this code.

## Extend your table with more features
<br>

No table worth rendering is going to have only two columns and three rows, like our example. Most likely, you're going to have a hefty portion of columns and row upon row of data. You'll need features to allow users to sift through all that data, like sorting, filtering, and pagination.

### Sorting

We want to give our customer service reps the ability to easily find what they are looking for, and sorting is a great way to accomplish that! If the reps want to see the most recent orders placed, they can sort by date from the **Date** column. If they want to scan through the customers alphabetically, they can sort by name in the **Name** column.

Sorting is accomplished by using the `useSortBy` Hook from react-table. Be sure to add that to your import statements:

```
import { useTable, useSortBy } from 'react-table' 
```

Next, you’ll need to pass `useSortBy` into the `useTable` Hook arguments:

```
const {
 getTableProps,
 headerGroups,
 rows,
 getRowProps,
 prepareRow,
} = 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 `sortType` field. Sorting options include:
* `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 example, we will be using the default, but if you needed to add that code, it would look like this:

```
const columns = React.useMemo(
 () => [
   {
     Header: 'Rating',
     accessor: 'rating',
     sortType: 'basic',
   },
 ],
 []
)
```

Now there are two more things to do. First, pass the `getSortByToggleProps()` function into your `getHeaderProps()` function. The `getSortByToggleProps()` function resolves props for toggling the sort direction when the user clicks on the header.

Second, add a `span` tag to display an arrow up, an arrow down, or nothing to the column header to indicate how that column is sorted. You can determine how the column is sorted by checking column properties `isSorted` and `isSortedDesc`.

```
<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>
```

![Retool-Styling-React-Table](https://retool.com/blog/content/images/2020/07/Retool-Styling-React-Table.gif)

Check out this [code sandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/sorting?file=/src/App.js:1275-1337) for a more advanced version of sorting using react-table.

### Filtering

For the sake of simplicity, this tutorial is going to focus on how to add a text filter on the columns of our simple table. This will give our customer support reps the ability to quickly find the information they are looking for. If a customer contacts them, the rep can easily search the **Names** column for that customer to find their orders or search the **Order Numbers** column to look up a specific order.

For further examples of all the different kinds of filters (including global, which is really useful), check out this the [react-table docs](https://react-table.tanstack.com/docs/api/useFilters). 

Filtering is accomplished by using the `useFilters()` Hook from react-table. Be sure to add that to your import statements:

```
import { useTable, useFilters } from 'react-table' 
```

Next, you’ll need to pass `useFilters` into the `useTable` Hook arguments:

```
const {
 getTableProps,
 headerGroups,
 rows,
 getRowProps,
 prepareRow,
} = useTable(
 {
   columns,
   data,
 },
 useFilters,
)
```

Now, we are going to add the UI for the column filter into our table. If there is a filter applied to this column, we render the UI for the filter by calling the column’s `render()` method on the Filter field. Otherwise, do nothing.

```
<th {...column.getHeaderProps()}>
 {column.render('Header')}
 <div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
```

But wait! We haven’t defined the UI for the filter yet. We’ll need a function to do that – for our filter function, we first want to find out how many rows are left to be filtered, so we can display that number to the user as an input placeholder. Then, we will render an `<input>` for the user to type what they want to filter.

```
function TextFilter({
 column: { filterValue, preFilteredRows, setFilter },
}) {
 const count = preFilteredRows.length
 return (
   <input
     value={filterValue || ''}
     onChange={e => {
       setFilter(e.target.value || undefined)
     }}
     placeholder={`Search ${count} records...`}
   />
 )
}
```

Our `TextFilter()` function receives three values from **column**:
* **filterValue** = the current value that this **column** is using to filter.
    * This value is set from the table’s state **filters** object.
* **preFilteredRows** = Array of rows passed to the column before any filtering was done.
* **setFilter** = function that takes in a **filterValue** in order to update the filterValue of this column (in this case, it’s taking the value that the user types into the ```<input>```).

Once we’ve defined our filter function, we’ll update the definition of our column object to have a `Filter` field. Add this code to your Table function:

```
const defaultColumn = React.useMemo(
 () => ({
   Filter: TextFilter,
 }),
 []
)
```

Finally, make sure to pass `defaultColumn` in with the columns and data arguments when you use `useTable()`:

```
const {
 getTableProps,
 ...
} = useTable(
 {
   columns,
   data,
   defaultColumn,
 },
 useFilters,
)
```

![Retool-React-Table-Filtering](https://retool.com/blog/content/images/2020/07/Retool-React-Table-Filtering.gif)

### Pagination

The data we created for the example in this tutorial is pretty small compared to what you would see in the real world. Only six rows? Please. In reality, our customer support reps would be dealing with hundreds (maybe even thousands) of rows of customer data. To avoid long render times and ginormous pages to scroll through, we are going to add pagination to our table. This will allow react-table to only deal with rendering some rows at a time and will take some strain off the customer support reps from having to look at overwhelming amounts of data.

Pagination is accomplished by using the `usePagination()` Hook from react-table. Be sure to add that to your import statements:

```
import { useTable, usePagination } from 'react-table' 
```

Next, you’ll need to pass `usePagination()` into the `useTable()` Hook arguments, set the initial state (if you want to start the `pageIndex` at anything other than 0 or have the `pageSize` bigger or smaller than 10), and extract extra properties from what it returns.

```
const {
   getTableProps,
   headerGroups,
   getRowProps,
   prepareRow,
   page,
   pageOptions,
   state: { pageIndex, pageSize },
   previousPage,
   nextPage,
   canPreviousPage,
   canNextPage,
 } = useTable(
   {
     columns,
     data,
     initialState: { pageSize: 2 },
   },
   usePagination,
 )
```

Note that as well as iterating through `rows` in `<tbody>` as we did previously before pagination, we are going to iterate through `page`, which is similar to `rows` except it only has the number of rows that fit on the page. If you don’t do this, you can click those buttons as much as you want — the data won’t move. Trust me.

```
<tbody {...getTableBodyProps()}>
 {page.map(row => {
   prepareRow(row)
   ...
```

In this example, we have a button to go to the Previous Page, a button to go to the Next Page, and an input that lets the user type a page number to jump to.

```
return (
   <div>
     <table {...getTableProps()}>
       ...
     </table>
     <div>
       <button onClick={() => previousPage()} disabled={!canPreviousPage}>
         Previous Page
       </button>
       <button onClick={() => nextPage()} disabled={!canNextPage}>
         Next Page
       </button>
       <div>
         Page{' '}
         <em>
           {pageIndex + 1} of {pageOptions.length}
         </em>
       </div>
     </div>
   </div>
 )
}
```

![Retool-React-Table-Pagination](https://retool.com/blog/content/images/2020/07/Retool-React-Table-Pagination.gif)

Check out this [code sandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/pagination) for a more advanced version of pagination using react-table.

## Your Table Component with react-table
<br>

Hopefully, this tutorial helps you understand how to create, style, and extend a table in React using react-table. For more advanced tutorials, we recommend checking out the “[Kitchen Sink](https://github.com/tannerlinsley/react-table/tree/master/examples/kitchen-sink)” example from react-table’s docs. It has fully fleshed-out examples of most things that react-table has to offer, from pagination and sorting to filtering, grouping, expandable rows, and row selection.