In this React 16 + tutorial, we’ll look into how to implement advanced Datatables in a React application having features like Filter, Pagination, Sorting, Row Column Resizing, Row Expand Collapse and many more.
Representing data not remained limited to simple HTML tables these days. Today we as a developer need some advanced forms on the web application to display information in more interactive ways.
We’ll implement datatable in the React application by using one of the best and topmost used package react-table. This package is beautifully built with some advanced and must have cool features required by users.
Let’s get started! I will try to explain each detail and color most of the important features with working examples. Do share your feedback in the comment section if anything left or challenging so that we can together seek it out.
[lwptoc]
Create a React Application
First, we’ll create a new React application using npx create-react-app
command
$ npx create-react-app react-datatable-app
Move inside the react app
$ cd react-datatable-app
Run application
$ npm start
Install react-table
 Package
After creating the React application ready, install the react-table
package by running below npm command
$ npm install react-table
Bootstrap Styling to Tables
By default, no style is provided by the react-table
package. As a quick and easy option, we’ll install the bootstrap
package to use its style on our tables.
Run the following command to install the bootstrap package.
$ npm install bootstrap
Creating Table Components
We’ll create function components for Tables in the components folder to create the different types of examples using react-table
package.
Create a new file ‘~src/components/basic.table.js‘ and update with the following code:
// src/components/basic.table.js
import React from "react";
import { useTable } from 'react-table'
import 'bootstrap/dist/css/bootstrap.min.css';
function BasicTableComponent() {
const columns = [
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Visits',
accessor: 'visits',
},
{
Header: 'Status',
accessor: 'status',
},
{
Header: 'Profile Progress',
accessor: 'progress',
},
],
},
];
const data = [
{
"firstName": "horn-od926",
"lastName": "selection-gsykp",
"age": 22,
"visits": 20,
"progress": 39,
"status": "single"
},
{
"firstName": "heart-nff6w",
"lastName": "information-nyp92",
"age": 16,
"visits": 98,
"progress": 40,
"status": "complicated"
},
{
"firstName": "minute-yri12",
"lastName": "fairies-iutct",
"age": 7,
"visits": 77,
"progress": 39,
"status": "single"
},
{
"firstName": "degree-jx4h0",
"lastName": "man-u2y40",
"age": 27,
"visits": 54,
"progress": 92,
"status": "relationship"
}
]
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
})
return (
<table className="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, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
)
}
export default BasicTableComponent;
Above we have some const
‘s defined columns
,data
to keep table columns definition and data which will be going to render inside the table.
The useTable
function provided by ‘react-table
‘ takes the columns
and data
objects and distribute values to properties we will be using inside the table using destructuring.
After that, we have a table to render the Header and table body cells by using map()
method on destructured properties
Render Basic Table in App Component
Now render this BasicTableComponent in the App.js function component as shown below:
// App.js
import React from 'react';
import './App.css';
import BasicTableComponent from './components/basic.table';
function App() {
return (
<div className="App">
<h3>Basic Table using <code>react-table</code></h3>
<BasicTableComponent />
</div>
);
}
export default App;
Run React app by hitting $ npm start
, it will look like this:
Table with Global and Filter for Each Column
Search filters can be added on each column of the Table and single filter for whole table cells. For that, we need to import the useFilters
and useGlobalFilter
functions.
Create a new function component at this location ‘~src/components/filter.table.js’ and update with following code:
// src/components/filter.table.js
import React from "react";
import { useTable, useFilters, useGlobalFilter, useAsyncDebounce } from 'react-table'
import 'bootstrap/dist/css/bootstrap.min.css';
// Define a default UI for filtering
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length
const [value, setValue] = React.useState(globalFilter)
const onChange = useAsyncDebounce(value => {
setGlobalFilter(value || undefined)
}, 200)
return (
<span>
Search:{' '}
<input
className="form-control"
value={value || ""}
onChange={e => {
setValue(e.target.value);
onChange(e.target.value);
}}
placeholder={`${count} records...`}
/>
</span>
)
}
function DefaultColumnFilter({
column: { filterValue, preFilteredRows, setFilter },
}) {
const count = preFilteredRows.length
return (
<input
className="form-control"
value={filterValue || ''}
onChange={e => {
setFilter(e.target.value || undefined)
}}
placeholder={`Search ${count} records...`}
/>
)
}
function Table({ columns, data }) {
const defaultColumn = React.useMemo(
() => ({
// Default Filter UI
Filter: DefaultColumnFilter,
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state,
preGlobalFilteredRows,
setGlobalFilter,
} = useTable(
{
columns,
data,
defaultColumn
},
useFilters,
useGlobalFilter
)
return (
<div>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
<table className="table" {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
{/* Render the columns filter UI */}
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<br />
<div>Showing the first 20 results of {rows.length} rows</div>
<div>
<pre>
<code>{JSON.stringify(state.filters, null, 2)}</code>
</pre>
</div>
</div>
)
}
function FilterTableComponent() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName'
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age'
},
{
Header: 'Visits',
accessor: 'visits'
},
{
Header: 'Status',
accessor: 'status'
},
{
Header: 'Profile Progress',
accessor: 'progress'
},
],
},
],
[]
)
const data = [
{
"firstName": "horn-od926",
"lastName": "selection-gsykp",
"age": 22,
"visits": 20,
"progress": 39,
"status": "single"
},
{
"firstName": "heart-nff6w",
"lastName": "information-nyp92",
"age": 16,
"visits": 98,
"progress": 40,
"status": "complicated"
},
{
"firstName": "minute-yri12",
"lastName": "fairies-iutct",
"age": 7,
"visits": 77,
"progress": 39,
"status": "single"
},
{
"firstName": "degree-jx4h0",
"lastName": "man-u2y40",
"age": 27,
"visits": 54,
"progress": 92,
"status": "relationship"
}
]
return (
<Table columns={columns} data={data} />
)
}
export default FilterTableComponent;
This will render a Table with Global and respective filter at the column level
Adding Pagination in Tables created by using react-table
To implement pagination on the table, we need to import the usePagination function. After that, we’ll add the template under the table to show Next, Previous, Select box to choose pages per page, etc.
Let’s create a new function component PaginationTableComponent at this location ‘~src/components/pagination.table.js’ and update with following code
// src/components/pagination.table.js
import React from "react";
import { useTable, usePagination } from 'react-table'
import 'bootstrap/dist/css/bootstrap.min.css';
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
initialState: { pageIndex: 2, pageSize: 5 },
},
usePagination
)
// Render the UI for your table
return (
<div>
<pre>
<code>
{JSON.stringify(
{
pageIndex,
pageSize,
pageCount,
canNextPage,
canPreviousPage,
},
null,
2
)}
</code>
</pre>
<table className="table" {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<ul className="pagination">
<li className="page-item" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
<a className="page-link">First</a>
</li>
<li className="page-item" onClick={() => previousPage()} disabled={!canPreviousPage}>
<a className="page-link">{'<'}</a>
</li>
<li className="page-item" onClick={() => nextPage()} disabled={!canNextPage}>
<a className="page-link">{'>'}</a>
</li>
<li className="page-item" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
<a className="page-link">Last</a>
</li>
<li>
<a className="page-link">
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</a>
</li>
<li>
<a className="page-link">
<input
className="form-control"
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px', height: '20px' }}
/>
</a>
</li>{' '}
<select
className="form-control"
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
style={{ width: '120px', height: '38px' }}
>
{[5, 10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</ul>
</div >
)
}
function PaginationTableComponent() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Visits',
accessor: 'visits',
},
{
Header: 'Status',
accessor: 'status',
},
{
Header: 'Profile Progress',
accessor: 'progress',
},
],
},
],
[]
)
const data = [
{
"firstName": "committee-c15dw",
"lastName": "editor-ktsjo",
"age": 3,
"visits": 46,
"progress": 75,
"status": "relationship"
},
{
"firstName": "midnight-wad0y",
"lastName": "data-7h4xf",
"age": 1,
"visits": 56,
"progress": 15,
"status": "complicated"
},
{
"firstName": "tree-sbdb0",
"lastName": "friendship-w8535",
"age": 1,
"visits": 45,
"progress": 66,
"status": "single"
},
{
"firstName": "chin-borr8",
"lastName": "shirt-zox8m",
"age": 0,
"visits": 25,
"progress": 67,
"status": "complicated"
},
{
"firstName": "women-83ef0",
"lastName": "chalk-e8xbk",
"age": 9,
"visits": 28,
"progress": 23,
"status": "relationship"
}]
console.log(JSON.stringify(data));
return (
<Table columns={columns} data={data} />
)
}
export default PaginationTableComponent;
This will render the table with pagination:
Sorting on Table Columns using react-table
To enable sorting of table columns we’ll import the useSortBy
function and update the useTable
function to pass it as an argument. On the table, the header adds Arrows to depict the sorted state of a column.
Update the ‘~src/components/sorting.table.js’ file with following code
// src/components/sorting.table.js
import React from "react";
import { useTable, useSortBy } from 'react-table'
import 'bootstrap/dist/css/bootstrap.min.css';
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable(
{
columns,
data,
},
useSortBy
)
// Render the UI for your table
return (
<div>
<table className="table" {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(
(row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
)
}
)}
</tbody>
</table>
<br />
<div>Showing the first 20 results of {rows.length} rows</div>
</div >
)
}
function SortingTableComponent() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Visits',
accessor: 'visits',
},
{
Header: 'Status',
accessor: 'status',
},
{
Header: 'Profile Progress',
accessor: 'progress',
},
],
},
],
[]
)
const data = [
{
"firstName": "committee-c15dw",
"lastName": "editor-ktsjo",
"age": 3,
"visits": 46,
"progress": 75,
"status": "relationship"
},
{
"firstName": "midnight-wad0y",
"lastName": "data-7h4xf",
"age": 1,
"visits": 56,
"progress": 15,
"status": "complicated"
},
{
"firstName": "tree-sbdb0",
"lastName": "friendship-w8535",
"age": 1,
"visits": 45,
"progress": 66,
"status": "single"
},
{
"firstName": "chin-borr8",
"lastName": "shirt-zox8m",
"age": 0,
"visits": 25,
"progress": 67,
"status": "complicated"
},
{
"firstName": "women-83ef0",
"lastName": "chalk-e8xbk",
"age": 9,
"visits": 28,
"progress": 23,
"status": "relationship"
},
{
"firstName": "women-83ef0",
"lastName": "chalk-e8xbk",
"age": 9,
"visits": 28,
"progress": 23,
"status": "relationship"
},
{
"firstName": "women-83ef0",
"lastName": "chalk-e8xbk",
"age": 9,
"visits": 28,
"progress": 23,
"status": "relationship"
},
{
"firstName": "women-83ef0",
"lastName": "chalk-e8xbk",
"age": 9,
"visits": 28,
"progress": 23,
"status": "relationship"
}]
console.log(JSON.stringify(data));
return (
<Table columns={columns} data={data} />
)
}
export default SortingTableComponent;
Table with sorting will look like this:
Expand/ Collapse Table Rows Hierarchy using react-table
We can easily implement the expand/collapse functionality in tables created using react-table
components. The data collection is created in the form of a parent-child structure with a modified column definition.
The useExpanded
function is imported to use inside the column definition.
Update the ‘~src/components/expanded.table.js’ file
// src/components/expandable.table.js
import React from "react";
import { useTable, useExpanded } from 'react-table'
import 'bootstrap/dist/css/bootstrap.min.css';
import makeData from './makeData'
function Table({ columns: userColumns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { expanded },
} = useTable(
{
columns: userColumns,
data,
},
useExpanded // Use the useExpanded plugin hook
)
// Render the UI for your table
return (
<div>
<table className="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, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<br />
<div>Showing the first 20 results of {rows.length} rows</div>
<pre>
<code>{JSON.stringify({ expanded: expanded }, null, 2)}</code>
</pre>
</div >
)
}
function ExpandableTableComponent() {
const columns = React.useMemo(
() => [
{
id: 'expander', // Make sure it has an ID
Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }) => (
<span {...getToggleAllRowsExpandedProps()}>
{isAllRowsExpanded ? '👇' : '👉'}
</span>
),
Cell: ({ row }) =>
row.canExpand ? (
<span
{...row.getToggleRowExpandedProps({
style: {
paddingLeft: `${row.depth * 2}rem`,
},
})}
>
{row.isExpanded ? '👇' : '👉'}
</span>
) : null,
},
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Visits',
accessor: 'visits',
},
{
Header: 'Status',
accessor: 'status',
},
{
Header: 'Profile Progress',
accessor: 'progress',
},
],
},
],
[]
)
const data = [
{
"firstName": "motion-cq4n1",
"lastName": "gold-c44qg",
"age": 5,
"visits": 89,
"progress": 70,
"status": "relationship",
"subRows": [
{
"firstName": "digestion-67zau",
"lastName": "presence-f0w8w",
"age": 17,
"visits": 89,
"progress": 67,
"status": "relationship",
"subRows": [
{
"firstName": "destruction-xbuvr",
"lastName": "growth-mrmei",
"age": 2,
"visits": 28,
"progress": 48,
"status": "complicated"
}
]
},
{
"firstName": "rifle-1kwh3",
"lastName": "awareness-qmhrt",
"age": 0,
"visits": 32,
"progress": 65,
"status": "complicated",
"subRows": [
{
"firstName": "aftermath-g4ydv",
"lastName": "mixture-hykkg",
"age": 11,
"visits": 94,
"progress": 70,
"status": "complicated"
}
]
}
]
},
{
"firstName": "philosophy-068q6",
"lastName": "sticks-07qdm",
"age": 9,
"visits": 47,
"progress": 6,
"status": "relationship",
"subRows": [
{
"firstName": "hole-eeai8",
"lastName": "historian-yhikw",
"age": 26,
"visits": 32,
"progress": 97,
"status": "relationship",
"subRows": [
{
"firstName": "stitch-lsuft",
"lastName": "suggestion-j7r61",
"age": 17,
"visits": 23,
"progress": 99,
"status": "single"
},
{
"firstName": "world-2wi9s",
"lastName": "courage-q0fvw",
"age": 20,
"visits": 27,
"progress": 1,
"status": "relationship"
}
]
},
{
"firstName": "pen-y8060",
"lastName": "magazine-uxycr",
"age": 6,
"visits": 57,
"progress": 83,
"status": "single",
"subRows": [
{
"firstName": "problem-393nd",
"lastName": "product-efasy",
"age": 12,
"visits": 13,
"progress": 1,
"status": "single"
}
]
}
]
}
]
console.log(JSON.stringify(data));
return (
<Table columns={columns} data={data} />
)
}
export default ExpandableTableComponent;
Table with expandable rows will look like this:
Render All Tables in App
Finally, we’ll render all table components in the App.js function component as shown below:
// App.js
import React from 'react';
import './App.css';
import BasicTableComponent from './components/basic.table';
import FilterTableComponent from './components/filter.table';
import PaginationTableComponent from './components/pagination.table';
import SortingTableComponent from './components/sorting.table';
import ExpandableTableComponent from './components/expandable.table';
function App() {
return (
<div className="App">
<h3>Basic Table using <code>react-table</code></h3>
<BasicTableComponent />
<h3>Filter Table using <code>react-table</code></h3>
<FilterTableComponent />
<h3>Table with Pagination using <code>react-table</code></h3>
<PaginationTableComponent />
<h3>Sorting on Table using <code>react-table</code></h3>
<SortingTableComponent />
<h3>Expandable on Table using <code>react-table</code></h3>
<ExpandableTableComponent />
</div>
);
}
export default App;
Source Code
Find the source code of this tutorial in my Github repository here.
Conclusion
React Table package provides a lot of useful components to enhance the features and functionalities to build datagrids. In this tutorial we discussed how to create a datatable and implement search filter globally and for each column. Pagination can also be implemented very easily on tables in React application.
We used Bootstrap styling for Table and other form controls. But we can easily customize them according to our needs.
Check out the official documentation here.
Leave a Reply