The Widlarz Group Blog
React Table 7 - Hooks Based Library
May 15, 2020
- React Table 7 - Hooks Approach to Creating Tables in React
- Differences and Migration From v6 to v7
- Starting with React Table 7
- Project Setup
- Prepare Data
- Define Columns
- Table Rendering - useTable Hook
- Add Bootstrap Table Style
- Custom Cell
- Sorting - useSortBy Hook
- Filtering - useFilters Hook
- Sub Components - useExpanded Hook
- Pagination - usePagingation Hook
- Conclusions
React Table 7 - Hooks Approach to Creating Tables in React
React table v7 is a lightweight (5-14kb), headless (100% customizable), and fully controllable tool for building fast and extendable data grids for React. The Library was created by Tanner Linsley, and it is clearly documented on Github.
Differences and Migration from v6 to v7
It’s true that there is a clear and significant difference between the two versions. Version 6 of React Table requires importing the main component and ready-made CSS styles. Both the functionality and the appearance of the UI are controlled by passing the appropriate props to the main component. The latest version of React Table (v7) is headless, which means that it doesn’t provide or support any UI elements. The responsibility for providing and rendering table markup rests solely with us. This gives us the opportunity to build a unique look and feel for our table. React Table v7 uses React Hooks both internally and externally for almost everything. It’s up to us to decide how to build and what functionalities to use. The provided collection of custom React Hooks brings us one step closer to achieving our goal.
You may have already used React Table v6 in the past. This version enjoys great popularity; that said, as the creator himself has pointed out, it could no longer be maintained. Perhaps this very fact or the architecture based on hooks (and thus its performance), will prompt you to switch to a newer version.
Starting with React Table 7
The best approach for starting with React Table v7 is learning by building a simple table, and then expanding it with new functionalities.
In this article, we start by building a simple table from scratch. With each subsequent step, we equip our table with new features such as sorting, filtering, sub-components, pagination and we add bootstrap styling as well.
Below is a photo of the final version we will build together, step by step. You can play with the table here: Demo.

At the end of each step, there’s a link to the current code.
Let’s begin then!
Project Setup
We start by creating a new React project using create-react-app
$ npx create-react-app table-example
The next step is to install the react-table
library
$ npm install react-table
or
$ yarn add react-table
Prepare Data
We use radnomuser API as data to fill the table
Our first task is to fetch 100 user contacts. We’ll do this with native JavaScript fetch and the React useEffect hook.
Each contact is a plain JS object that consists of information such as name, location, gender, contact details and picture.
import React, { useEffect, useState } from "react"
const App = () => {
const [data, setData] = useState([])
useEffect(() => {
const doFetch = async () => {
const response = await fetch("https://randomuser.me/api/?results=100")
const body = await response.json()
const contacts = body.results
console.log(contacts)
setData(contacts)
}
doFetch()
}, [])
return <div>Hello</div>
}
Define Columns
Once we have our data all ready, the next step is to define the column structure (an array of objects that consists of header - column name and accessor - key in data). For optimization purposes, we wrap our columns in the React useMemo hook.
const columns = useMemo(
() => [
{
Header: "Title",
accessor: "name.title",
},
{
Header: "First Name",
accessor: "name.first",
},
{
Header: "Last Name",
accessor: "name.last",
},
{
Header: "Email",
accessor: "email",
},
{
Header: "City",
accessor: "location.city",
},
],
[]
)

Table Rendering - useTable Hook
The first and most important hook we’ll use is useTable
useTable requires an object with two properties: data (our contacts) and columns, which we have previously defined. The hook returns properties that we need to destructre, and we need those to build our table. Their purpose and place are explained in the code below.
We start building our Table in a separate component TableContainer.js
This is the basic version of the table, which will be the base for implementing additional functionalities.
Please note how our destructured properties from the useTable hook are used.
// App.js
import TableContainer from "./TableContainer"
return <TableContainer columns={columns} data={data} />
// TableContainer.js
import React from "react"
import { useTable } from "react-table"
const TableContainer = ({ columns, data }) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
})
return (
// If you're curious what props we get as a result of calling our getter functions (getTableProps(), getRowProps())
// Feel free to use console.log() This will help you better understand how react table works underhood.
<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>
)
}
export default TableContainer
All of the code we have written so far, can be found here. Don’t forget to star the repo if you find it useful!
Add Bootstrap Table Style
As we mentioned earlier, version 7 of React Table does not support any UI view. The style of the table is up to us. In our example, we utilize the Bootstrap appearance for this purpose. To do this, we need to install two packages.
$ yarn add bootstrap
$ yarn add reactstrap
The first thing to do is to import bootstrap styles. A bootstrap container is also added in order to position the table into the center.
// App.js
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css"
// return <TableContainer columns={columns} data={data} />;
return (
<Container style={{ marginTop: 100 }}>
<TableContainer columns={columns} data={data} />
</Container>
)
After importing bootstrap styles, all we need to do is replace the HTML table with the Bootstrap Table component.
// TableContainer.js
import { Table } from 'reactstrap';
// <table {...getTableProps()}>
<Table bordered hover {...getTableProps()}>
The code with added bootstrap is available here
Custom Cell
React Table 7 allows you to define a custom look for each cell. We can do this in the definition for a given column. As far as Table Cell goes, we can render any React Component.
{
Header: 'Color',
accessor: 'color'
// Cell has access to row values. If you are curious what is inside cellProps, you can console log it
Cell: (cellProps) => {
return <YourReactComponent {...cellProps}/>
}
}
In our example we are going to create a new column - Hemisphere, which we use to render the hemisphere sign based on user coordinates. In accessor
, we will destructure the latitude and longitude values to determine the user’s hemisphere.
{
Header: 'Hemisphere',
accessor: (values) => {
const { latitude, longitude } = values.location.coordinates;
const first = Number(latitude) > 0 ? 'N' : 'S';
const second = Number(longitude) > 0 ? 'E' : 'W';
return first + '/' + second;
}
},
Next, we render the respective sign.
{
Header: 'Hemisphere',
accessor: (values) => {
const { latitude, longitude } = values.location.coordinates;
const first = Number(latitude) > 0 ? 'N' : 'S';
const second = Number(longitude) > 0 ? 'E' : 'W';
return first + '/' + second;
},
// we can also write code below as a separate React Component
Cell: ({ cell }) => {
const { value } = cell;
const pickEmoji = (value) => {
let first = value[0]; // N or S
let second = value[2]; // E or W
const options = ['⇖', '⇗', '⇙', '⇘'];
let num = first === 'N' ? 0 : 2;
num = second === 'E' ? num + 1 : num;
return options[num];
};
return (
<div style={{ textAlign: 'center', fontSize: 18 }}>
{pickEmoji(value)}
</div>
);
}
},
Link to the current code. Don’t forget to star the repo if you find it useful!
Sorting - useSortBy Hook

React Table 7 allows us to easily create sorting for our table. To create sorting, we will need another hook from the React Table hooks collection - useSortBy
We pass the useSortBy hook as a parameter to our main useTable hook. React Table automatically handles sorting in ascending/descending order.
import { useTable, useSortBy } from "react-table"
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable(
{
columns,
data,
},
useSortBy
)
All we need to do is to make a minor change in the way we render column headers.
In the column header definition, we invoke the function getSortByToggleProps
on a column - which returns an onClick event responsible for changing the sorting direction. We have included generateSortingIndicator
- a helper function which returns the sort indicator based on sorting state (ascending/descending/no sorting). Code below.
// <th {...column.getHeaderProps()}>{column.render('Header')}</th>
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
{generateSortingIndicator(column)}
</th>
const generateSortingIndicator = column => {
return column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""
}
With this implementation, we can do sorting on each column by default. If we want to disable sorting in a particular column, we need to set the disableSortBy
property to true in the column definition.
{
Header: 'Title',
accessor: 'name.title'
disableSortBy: true
},
You can find the code for the table with sorting here. Again, if you haven’t done it already - don’t forget to star the repo if you find it useful!
Filtering - useFilters Hook

The next feature that we will add to our table is column filtering. With the useFilters hook, we can do it an easy and accessible way.
Like before, we need to pass the useFilters hook as a parameter to our main useTable hook. useFilters must be placed before useSortBy, otherwise React Table 7 will inform us about that fact in the console. (Error: React Table: The useSortBy plugin hook must be placed after the useFilters plugin hook!
)
import { useTable, useSortBy, useFilters } from "react-table"
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable(
{
columns,
data,
},
useFilters,
useSortBy
)
After connecting the useFilters hook, the next step will be to modify the way of rendering for our <th>
. We add the <div>
so that clicking on our filter does not trigger sorting in a column.
// <th {...column.getHeaderProps(column.getSortByToggleProps())}>
// {column.render('Header')}
// {generateSortingIndicator(column)}
<th {...column.getHeaderProps()}>
<div {...column.getSortByToggleProps()}>
{column.render("Header")}
{generateSortingIndicator(column)}
</div>
<Filter column={column} />
</th>
Let’s create a new file filters.js
, in which we are going to write views for our filters :)
Filter
- a universal component for rendering a filter view
import React from "react"
export const Filter = ({ column }) => {
return (
<div style={{ marginTop: 5 }}>
{column.canFilter && column.render("Filter")}
</div>
)
}
Now, we are going to write our two filters, which we want to use in our table.
The first filter DefaultColumnFilter
- will render Text Input, which filters based on the entered text (the text filtering functionality is provided by default by the React Table). The second of them, SelectColumnFilter
, renders Select Input which allows to choose from the available options.
import { Input, CustomInput } from "reactstrap"
export const DefaultColumnFilter = ({
column: {
filterValue,
setFilter,
preFilteredRows: { length },
},
}) => {
return (
<Input
value={filterValue || ""}
onChange={e => {
setFilter(e.target.value || undefined)
}}
placeholder={`search (${length}) ...`}
/>
)
}
export const SelectColumnFilter = ({
column: { filterValue, setFilter, preFilteredRows, id },
}) => {
const options = React.useMemo(() => {
const options = new Set()
preFilteredRows.forEach(row => {
options.add(row.values[id])
})
return [...options.values()]
}, [id, preFilteredRows])
return (
<CustomInput
id="custom-select"
type="select"
value={filterValue}
onChange={e => {
setFilter(e.target.value || undefined)
}}
>
<option value="">All</option>
{options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</CustomInput>
)
}
How to use our filters?
DefaultColumnFilter
- is passed as a default filter for columns to the useTable hook, which means that each of our column uses this filter until it is turned off or another filter is attached.
// TableContainer.js
import { Filter, DefaultColumnFilter } from './filters';
useTable(
{
columns,
data
defaultColumn: { Filter: DefaultColumnFilter }
}
);
SelectColumnFilter
- adding the “Filter” prop in Column Definition (overrides the default filter).
// App.js
import { SelectColumnFilter } from './filters';
{
Header: 'Title',
accessor: 'name.title',
Filter: SelectColumnFilter,
filter: 'equals' // by default, filter: 'text', but in our case we don't want to filter options like text, we want to find exact match of selected option.
},
If you don’t want to display any filter in a column, simply add this line of code in for that column:
disableFilters: true
The code for the table with added filters is available here.
Sub Components - useExpanded Hook

The first step is to import the useExpanded hook from React Table and join it with our useTable. Next a visibleColumns
prop from useTable is destructured, to make sure that our Sub Component will take 100% of the table width.
import { useTable, useSortBy, useFilters, useExpanded } from 'react-table';
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
visibleColumns,
} = useTable(
{
columns,
data,
defaultColumn: { Filter: DefaultColumnFilter },
},
useFilters,
useSortBy
useSortBy,
useExpanded
);
To display the Sub-Component, we need to modify the rendering for a table body <tr>
We use the value row.isExpanded
to determine whether we want to display the Sub-Component.
// <tr {...row.getRowProps()}>
// {row.cells.map(cell => {
// return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>;
// })}
// </tr>
<Fragment key={row.getRowProps().key}>
<tr>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
})}
</tr>
{row.isExpanded && (
<tr>
<td colSpan={visibleColumns.length}>{renderRowSubComponent(row)}</td>
</tr>
)}
</Fragment>
We use the App.js
file to write the renderRowSubComponent
function as well as the column definition with emoji indicating whether our row is open or not.
// Add at the beginning of column definition
{
Header: () => null,
id: 'expander', // 'id' is required
Cell: ({ row }) => (
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'}
</span>
)
},
The renderRowSubComponent
function, renders a simple Bootstrap Card with contact details. We need to pass it as a prop to TableContainer
.
// import { Container } from 'reactstrap';
import {
Container,
Card,
CardImg,
CardText,
CardBody,
CardTitle,
} from "reactstrap"
const renderRowSubComponent = row => {
const {
name: { first, last },
location: { city, street, postcode },
picture,
cell,
} = row.original
return (
<Card style={{ width: "18rem", margin: "0 auto" }}>
<CardImg top src={picture.large} alt="Card image cap" />
<CardBody>
<CardTitle>
<strong>{`${first} ${last}`} </strong>
</CardTitle>
<CardText>
<strong>Phone</strong>: {cell} <br />
<strong>Address:</strong> {`${street.name} ${street.number} - ${postcode} - ${city}`}
</CardText>
</CardBody>
</Card>
)
}
// pass the function 'renderRowSubComponent' as a prop to our TableContainer
// <TableContainer columns={columns} data={data} />
<TableContainer
columns={columns}
data={data}
renderRowSubComponent={renderRowSubComponent}
/>
After implementing the above steps, you should have a working sub-components functionality after clicking the right emoji.
The code can be found here.
Pagination - usePagingation Hook

The last hook we are going to implement is usePagination. As always, we need to add usePagination to our useTable.
This hook requires destructuring of several additional props that we need to build our pagination.
In addition, we define initialState in which we specify how many rows we want to display (pageSize
) and from which page we start displaying (pageIndex
).
import { useTable, useSortBy, useFilters, useExpanded, usePagination } from 'react-table';
const {
getTableProps,
getTableBodyProps,
headerGroups,
// rows, -> we change 'rows' to 'page'
page,
prepareRow,
visibleColumns
// below new props related to 'usePagination' hook
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize }
} = useTable(
{
columns,
data,
defaultColumn: { Filter: DefaultColumnFilter }
initialState: { pageIndex: 0, pageSize: 10 }
},
useFilters,
useSortBy,
useExpanded
usePagination
);
Instead of using rows
, we use page
(which only keeps rows for an active page),
// <tbody {...getTableBodyProps()}>
// {rows.map(row => {
// prepareRow(row);
<tbody {...getTableBodyProps()}>
{page.map(row => {
prepareRow(row);
Let’s create two helper functions that can handle changing pages both in Text Input and Select Input
const onChangeInSelect = event => {
setPageSize(Number(event.target.value))
}
const onChangeInInput = event => {
const page = event.target.value ? Number(event.target.value) - 1 : 0
gotoPage(page)
}
Then, below <Table>
definition we create our pagination. This is only an example - you can build and style it in any way you want!
// import { Table } from 'reactstrap';
import { Table, Row, Col, Button, Input, CustomInput } from "reactstrap"
<Fragment>
<Table>{/* our table code here ... */}</Table>
<Row style={{ maxWidth: 1000, margin: "0 auto", textAlign: "center" }}>
<Col md={3}>
<Button
color="primary"
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
>
{"<<"}
</Button>
<Button
color="primary"
onClick={previousPage}
disabled={!canPreviousPage}
>
{"<"}
</Button>
</Col>
<Col md={2} style={{ marginTop: 7 }}>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>
</Col>
<Col md={2}>
<Input
type="number"
min={1}
style={{ width: 70 }}
max={pageOptions.length}
defaultValue={pageIndex + 1}
onChange={onChangeInInput}
/>
</Col>
<Col md={2}>
<CustomInput type="select" value={pageSize} onChange={onChangeInSelect}>
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</CustomInput>
</Col>
<Col md={3}>
<Button color="primary" onClick={nextPage} disabled={!canNextPage}>
{">"}
</Button>
<Button
color="primary"
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
>
{">>"}
</Button>
</Col>
</Row>
</Fragment>
If you have implemented the above steps, you should now have a working pagination for your table. usePagination is the last hook we have used to build our table.
You can find the whole code here.
Conclusions
Voilà! We have a ready table that is equipped with some interesting features. The table made in this article is merely an introduction to building more advanced, custom tables with the help of React Table 7!
In fact, there is a whole another spectrum of functionalities that we can get with React Table 7. It’s up to us how we will construct our table. For more information and examples please refer to this great documentation.

Written by Bartek Bajda.