Ask a frontend developer what trivial HTML element they spend the most time working on tirelessly; they will most likely say the <table> element. On the surface, it seems quite a simple thing with a parent tag having multiple children tags like <td>, <thead>, <th> etc. But the more you dig deep into making professional-looking data tables, the more frustrating the experience becomes.

React devs, rejoice! Developers like you at TanStack made React Table library to solve the complex problems we face most of the time. And yes, this can be the most potent library out there to render a table with so many exciting features. Let’s have a look!

What is React Table?

In short, React Table is a library for React.js to build lightweight and extensible data-based tables.

Or in other words,

React Table is a collection of React Hooks that helps you build powerful tables and datagrid experiences.

All the Hooks included are lightweight, composable, and very extensible. The best part is that all of this is ‘headless which means you do not get any markup or a default rendered style out of the box! This is one of the many ways React Table is extensible and suited to your needs, and you are in complete control of how to style it.

One clear distinction to draw here is that React-Table is NOT a table component to render. Instead, it’s a headless table utility. You can integrate it with any existing theme, library, or markup.

Why choose React Table?

Some of the cool features this library provides over others are as follows:

  1. Headless all the way: as it’s just a Hook, that’s why it’s ‘headless’ by design. You are in complete control of how the rendered table looks.
  2. Robust data-specific features and declarative: you can take a new or existing table(s) and then put React Table to its test by adding filters, sorting, grouping, aggregating, paginating, or using massive data sets with its small API footprint!
  3. Highly extensible: React Table comes with its plugin ecosystem and allows you to override or extend anything you like. What’s more, all the plugins are also Hooks by themselves! 

Some of the other features of React Table include:

  • The small size of 5-14kb
  • Custom cell formatting 
  • Sorting with multi-sort
  • Global and column filters
  • Pagination
  • Row grouping and aggregation
  • Row selection and expansion
  • Column order, resizing, and hiding
  • Server-side data models
  • Nested/group headers, footers
  • Sub-row components
  • Table, Flex, and Grid helpers

Now we know enough of what React Table does and what all the great features it allows. Now let’s create a quick demo with it.

Install React Table

The library is compatible with React v16.8+ and can work efficiently with ReactDOM and React Native

Now let’s open a terminal window and run the following command after we have created a new React app:

npm install react-table --save
// OR
yarn add react-table

With this, you should see React Table dependency added to your package.json file:

<code>"dependencies": {</code>
...
  "react-table": "latest",
...
},

Also Read: 8 Best Tools for React Ecosystem in 2022

What will we be making?

Here’s what our final result will look like:

As you see, it’s a basic table with rows, columns, some data, and a bit of custom styling. We will be fetching the data from a third-party API provider. More on this later, but first, let’s start with the table’s structure, i.e., create the initial JSX.

Step 1: Write the JSX

Open the App.jsx file and remove all the default code it has. We will be creating our react table components from scratch.

Create a new function, App()and let’s first assume that we will have a loading state when the data is fetched from the API. We will show the user a “Loading…” text until it loads. When it finishes, the table will be rendered. Adding this fallback text is crucial for situations where you may have a massive list of data to show upfront, and you don’t want to sacrifice your app’s performance.

Here are some of the react table examples that will help you out. So let’s use the useState() Hook from React has a loadingData and setLoadingData states:

<code>const [loadingData, setLoadingData] = useState(true);</code>

Now, in the return statement, let’s conditionally render our loading and react table components:

<code>return (<Styles> {</code>loadingData
    ? (<p>Loading...</p>)
    : (<Table columns={columns}
      data={data}/>)
} </Styles>);

 Don’t forget to export this fundamental component with export default App;. 

Step 2: Render the table UI

Let’s create a new function called  Table() and pass in two props to its columns and data.

Now with the useTable Hook from the react-table dependency, we will get five functions from it:

<code>const {</code>  getTableProps, getTableBodyProps, headerGroups, rows, prepareRow
}

= useTable({ columns, data });

 Here’s a description of each of them:

  • getTableProps: this is a required function to resolve any props needed in the table wrapper.
  • getTableBodyProps: another required function to determine any necessary props for your table body wrapper.
  • headerGroups: this is an array of table header groups; each will have an object/array to show as a child header.
  • rows: this array of row objects will get data from the `data` array and `columns` passed to the table options.
  • prepareRow: a final required function responsible for lazily preparing a row for rendering.

Now let’s return the table structure UI we need inside a return statement. This will be like a usual HTML <table> JSX with the props and functions of React Table library.

<code><table {...getTableProps()}></code>  <thead>
    {headerGroups.map((headerGroup) => (
    <tr {...headerGroup.getHeaderGroupProps()}>
      {headerGroup.headers.map((column) => (
      <th {...column.getHeaderProps()}>{column.render("Header")}</th>
      ))}
    </tr>
    ))}
  </thead>

Also Read: Angular vs React vs Vue: Top Developer’s choice in 2022

First, we have a <table> where we will spread the getTableProps() function. Next, <thead> will be the header for our table. Here, we will map the headerGroups to show the individual <tr> getting the getHeaderGroupProps(). Inside of each <tr> we will have a child <th> where we will spread the column prop along with its equivalent getHeaderProps() function. Here, we must call the render() function. This takes in a string “Header” that will act as a reference when we will structure our data fetching function.

<code><tbody {...getTableBodyProps()}></code>{rows.map((row, i) => {
   prepareRow(row);
   return (
  <tr {...row.getRowProps()}>
    {row.cells.map((cell) => {
     return
    <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
       })}
  </tr>
  );
   })}
</tbody>

After this, we will work on the <tbody> that will have all our cells. This element will take in the spread getTableBodyProps() function, inside which we will map the rows. First, we use the prepareRow() Hook passing in the individual row we want to render, then we’ll return the JSX similar to <thead> above.

Here’s our entire Table function:

<code>function Table({columns, data}) {</code> const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable({columns, data});
  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, i) => {
        prepareRow(row);
        return (<tr {...row.getRowProps()}> {
          row.cells.map((cell) => {
            return <td {...cell.getCellProps()}> {
              cell.render("Cell")
            }</td>;
          })
        } </tr>);
      })
    } </tbody>
  </table>);
}

Step 3: Fetch the table data

Now that we have our table structure ready, what to fill inside? For that, we will be using a list of users provided to us by the JSONPlaceholder API service for testing. The URL endpoint we will be using is:

 https://jsonplaceholder.typicode.com/users.

Inside the App() function, add a new useState() Hook having data and setData as:

<code>const [data, setData] = useState([]);</code>

The data was the first prop we passed onto the Table() function above. Next was the columns so that is handled as:

<code>const columns = React.useMemo(() => [</code>{
    Header: "General information",
    columns: [
      {
        Header: "Id",
        accessor: "id"
      }, {
        Header: "Name",
        accessor: "name"
      }, {
        Header: "Username",
        accessor: "username"
      }, {
        Header: "Company",
        accessor: "company.name"
      }
    ]
  }, {
    Header: " Contact information",
    columns: [
      {
        Header: "Email",
        accessor: "email"
      }, {
        Header: "City",
        accessor: "address.city"
      }, {
        Header: "Phone",
        accessor: "phone"
      }, {
        Header: "Website",
        accessor: "website"
      }
    ]
  }
], []);

As you can see, it uses useMemo() Hook from React, which the React Table library requires to work correctly. And it returns an array or an object having the Header string along with the columns object with its required option of the accessor. This is used to build the data model for our table column, and it should match the JSON data we get back from the API.

Now let’s tackle the fetching. For this, we will use the useEffect() Hook along with the Axios library.

<code>useEffect(() => {</code>async function getData() {
    await axios.get("https://jsonplaceholder.typicode.com/users").then((response) => {
      setData(response.data);
      setLoadingData(false);
    });
  }
  if (loadingData) {
    getData();
  }
}, [loadingData]);

We create the asynchronous getData() function, which awaits the data fetched from Axios’s get() method holding the URL to the user information list stored as JSON. With setData(response.data), we are populating the data, and then after this is successful, we tell the loading state to be false so that it hides and we can see the table data.

If you did all the steps correctly, you should have the rendered table with the contents inside it fetched from an API source!

You are free to style your table with vanilla CSS, a css-in-js approach library like Styled Components, or an entire framework like MUI! Here’s the complete code for our app:

<code>import React, {useState, useEffect} from "react";</code>import {useTable} from "react-table";
import axios from "axios";

function Table({columns, data}) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = useTable({columns, data});
  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, i) => {
        prepareRow(row);
        return (<tr {...row.getRowProps()}> {
          row.cells.map((cell) => {
            return <td {...cell.getCellProps()}> {
              cell.render("Cell")
            }</td>;
          })
        } </tr>);
      })
    } </tbody>
  </table>);
}

function App() {
  const [loadingData, setLoadingData] = useState(true);
  const [data, setData] = useState([]);
  const columns = React.useMemo(() => [
    {
      Header: "General information",
      columns: [
        {
          Header: "Id",
          accessor: "id"
        }, {
          Header: "Name",
          accessor: "name"
        }, {
          Header: "Username",
          accessor: "username"
        }, {
          Header: "Company",
          accessor: "company.name"
        }
      ]
    }, {
      Header: " Contact information",
      columns: [
        {
          Header: "Email",
          accessor: "email"
        }, {
          Header: "City",
          accessor: "address.city"
        }, {
          Header: "Phone",
          accessor: "phone"
        }, {
          Header: "Website",
          accessor: "website"
        }
      ]
    }
  ], []);

  useEffect(() => {
    async function getData() {
      await axios.get("https://jsonplaceholder.typicode.com/users").then((response) => {
        setData(response.data);
        setLoadingData(false);
      });
    }
    if (loadingData) {
      getData();
    }
  }, [loadingData]);

  return (<> {
    loadingData
      ? (<p>Loading...</p>)
      : (<Table columns={columns}
        data={data}/>)
  } </>);
}

export default App;

Now that you know about creating a basic table from React Table in your React apps, along with how to add the data fetched from an external API source, you can go ahead with adding some filters, sorting, grouping, and more!

 If you are looking for more react table variations, you can browse our react templates, which have many additional features.