Blog
Deep links

React Table 7 - Hooks Based Library

React Table 7 - hooks based library to create beautiful, custom tables in React. Build a fully-fledged table step by step with us, implement the most common features like sorting, filtering, pagination and more. Learn how v7 is different from v6.
Tags
React
Hooks
React table
Bootstrap
By
Bartek Bajda
May 15, 2020
5 minute read

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.

photo-1



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",
    },
  ],
  []
)
data-1

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

sorting

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

filtering

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 component that will display the view for our filters. On top of that, we wrap the rest of the header cell in <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}) ...`}
    />
  )
}

How to use our filters?

  1. 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 }
    }
  );
  1. 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

Sub Components

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

pagination

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.

React
Hooks
React table
Bootstrap
Bartek Bajda
Category
January 5, 2022

Build and release your app with Fastlane!

Continuous Deployment
Fastlane
React Native
Mobile development
Ruby
In this article you will learn how to save your time. Try out this Fastlane setup for both Android and iOS!
See article
Category
December 6, 2022

Deep Links, Universal and Asset Links

Linking
React Native
Universal Links
Deep Links
Asset links
Do you want to dive deep into the topic of Deep Links, Universal Links and App Links? Wait no more and take a read!
See article
Category
July 19, 2021

Creating custom animated bottom tab and drawer navigation

React-navigation/native
React-navigation/bottom-tabs
React Native
Mobile development
Reanimated
I'll show you my way of handling React Native app navigation and we'll build application with custom animated bottom navigation tab.
See article
Do you need help with developing react solutions?

Leave your contact info and we’ll be in touch with you shortly

Leave contact info
Become one of our 10+ ambassadors and earn real $$$.
By clicking “Accept all”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.