Editor’s Note: This blog post was reviewed on 3 April 2023 for accuracy and to include information on TanStack Table v8. You can learn more about mutations in TanStack Query and how it compares to SWR, as well as tune into our PodRocket episode with Tanner Linsey, the creator of TanStack.
As one of the most popular ways to organize complex data, table UIs are very common in web products. However, building a table UI from scratch can be a tall order, and React in particular is known for giving developers a headache.
Fortunately, there is a wide variety of tools and libraries available to make the experience of creating a React table much simpler and more rewarding, most notably TanStack Table, formerly called React Table.
In this tutorial, we’ll show you how to build a smart React data table UI with basic sorting and searching functionalities. At the time of writing, the migration guide for TanStack Table v8 is not yet available. Therefore, we’ll use the latest stable version of React Table, v7.
Some common use cases for table UIs in React include displaying data for financial reports, sports leaderboards, and pricing and comparison pages, just to name a few:
Some products that use tables extensively include Airtable, Asana List View, Asana Timeline, Google Sheets, and Notion Table. Some tech giants that use React Table include Google, Apple, and Microsoft.
React Table is one of the most widely used table libraries in React. It has more than 20,000 stars on GitHub at the time of writing, receives frequent updates, and supports Hooks. The React Table library is very lightweight and offers all the basic features necessary for a simple table.
In March 2020, React Table creator Tanner Linsley released React Table v7, which he described as “the culmination of over a years worth of work to refactor the entire library to a Hooks-only UI/Style/Markup agnostic table building utility.”
React Table v7 is comprised of a collection of React Hooks and plugins designed to help you compose logical features of complex data grids into a single, performant, extensible, and unopinionated API, which is returned by the primary useTable
Hook.
As a headless utility, React Table doesn’t render or supply data table UI elements out of the box. Therefore, you’re responsible for rendering your own table markup using the state and callback of the React Table hooks.
On 1 July 2022, Tanner announced the release of TanStack table, which offers a major upgrade from React Table v7.
TanStack Table v8 is designed to be more performant and feature-rich than v7, supporting additional web frameworks like Vue, Solid, and Svelte. Later in this article, we’ll see what a potential migration from React Table v7 might look like.
You should consider React Table when your table UI needs any of the following:
You should consider using another React data table library when you need either of the following:
React Table doesn’t dictate the UI; it’s headless. So, it’s our responsibility to define the UI based on our needs.
Although we can achieve support for inline editing columns in React Table, it’s out of scope for our table to do it. We need to create a plugin or component on top of React Table to support such features. React Table stays true to its name and is best for rendering simple tables.
Performance-wise, React Table can’t handle infinitely long tables, like a Google Sheet. React Table works well for medium-sized tables, but not for long ones.
Ultimately, you should consider React Table for simple tables that need basic features, like searching, sorting, filtering, etc. A few good examples might include a sports leaderboard with statistics or a financial data table with custom elements.
In this tutorial, we’ll demonstrate how to build a simple Airtable clone using React Table. But first, let’s quickly review the features of a fully functional React Table UI and discuss some common challenges associated with building data tables in React.
Some basic features of a React data table UI include:
Advanced features in a React data table UI might include:
UI-wise, data tables are one of the best options to show complex data in an organized way. But, UX-wise, tables are tricky, and they can easily get out of hand when you support multiple devices.
For example, it’s difficult to make a table responsive without changing the layout to suit smaller screen sizes. Additionally, a table might need scrolling in both directions. Default browser scrollbars will work well for full-width tables, but most tables are of a custom width. Custom scrollbars are very tricky to support on both touch and non-touch screens.
Managing the width of a column based on data length is tricky. When we load dynamic data in the table, we often encounter UX glitches. Each time the data changes, the column width is resized, causing an alignment glitch. We need to be careful about handling these issues when designing our UX.
You’ll want to consider building your own React table UI in the following scenarios:
Below are some common use cases for building your own table in React:
Table
componentEnough theory; let’s dive into a real example. To demonstrate how to create a Table
component in React Table, we’ll build a simple table UI with basic functionalities like sorting and searching. Here is the React Table example we’ll be working with.
First, create a new React app using Create React App:
npx create-react-app react-table-demo
For our application, we’ll use Axios to retrieve movie information with the search term snow
from the TVMAZE API. Below is the endpoint for this operation:
https://api.tvmaze.com/search/shows?q=snow
To call the API, let’s install Axios:
npm install axios # OR yarn add axios
Next, replace the content of the default arc/App.js
file with the code below:
// App.js import axios from "axios"; import { useState, useEffect } from "react"; import Table from "./Table"; import "./App.css"; function App() { // data state to store the TV Maze API data. Its initial value is an empty array const [data, setData] = useState([]); // Using useEffect to call the API once mounted and set the data useEffect(() => { (async () => { const result = await axios("https://api.tvmaze.com/search/shows?q=snow"); setData(result.data); })(); }, []); return <div className="App"></div>; } export default App;
Above, we created a state called data
. Once the component gets mounted, we fetch movies content from the TVMAZE API using Axios and set the returned result to data
.
Add React Table with the command below:
npm install react-table # OR yarn add react-table
React Table uses React Hooks. It has a main table Hook called useTable
, as well as a plugin system to add plugin Hooks. Therefore, React Table is easily extensible based on our custom needs.
Let’s create our basic UI with the useTable
Hook. To proceed, we’ll create a new Table
component that will accept two props, data
and columns
. The data
prop is the data we got through the API call, and columns
is the object to define the table columns, headers, rows, how the row will be shown, etc. We’ll see it in our code shortly:
In the /src
folder, create a new Table.js
file and paste the following code:
// Table.js export default function Table({ columns, data }) { // Table component logic and UI come here }
Let’s modify the content in App.js
to include the columns for our table and also render the Table
component:
// App.js import axios from "axios"; import React, { useMemo, useState, useEffect } from "react"; import Table from "./Table"; import "./App.css"; function App() { /* - Columns is a simple array right now, but it will contain some logic later on. It is recommended by react-table to memoize the columns data - Here in this example, we have grouped our columns into two headers. react-table is flexible enough to create grouped table headers */ const columns = useMemo( () => [ { // first group - TV Show Header: "TV Show", // First group columns columns: [ { Header: "Name", accessor: "show.name", }, { Header: "Type", accessor: "show.type", }, ], }, { // Second group - Details Header: "Details", // Second group columns columns: [ { Header: "Language", accessor: "show.language", }, { Header: "Genre(s)", accessor: "show.genres", }, { Header: "Runtime", accessor: "show.runtime", }, { Header: "Status", accessor: "show.status", }, ], }, ], [] ); // data state to store the TV Maze API data. Its initial value is an empty array const [data, setData] = useState([]); // Using useEffect to call the API once mounted and set the data useEffect(() => { (async () => { const result = await axios("https://api.tvmaze.com/search/shows?q=snow"); setData(result.data); })(); }, []); return ( <div className="App"> <Table columns={columns} data={data} /> </div> ); } export default App;
In the code above, we used the useMemo
Hook to create a memoized array of columns; we defined two level headers, each with different columns for our table heads.
We’ve set up all of the columns to have an accessor, which is the data returned by the TVMAZE API set to data
. Because our data is contained within the show
object in the array, all of our accessors begin with show
:
// sample data array looks like this [ { "score": 17.592657, "show": { "id": 44813, "url": "http://www.tvmaze.com/shows/44813/the-snow-spider", "name": "The Snow Spider", "type": "Scripted", "language": "English", "genres": [ "Drama", "Fantasy" ], "status": "In Development", "runtime": 30, "premiered": null, "officialSite": null, "schedule": { "time": "", "days": [ ] } ... }, { // next TV show } ... ]
Now, let’s finish our Table
component:
// Table.js import React from "react"; import { useTable } from "react-table"; export default function Table({ columns, data }) { // Use the useTable Hook to send the columns and data to build the table const { getTableProps, // table props from react-table getTableBodyProps, // table body props from react-table headerGroups, // headerGroups, if your table has groupings rows, // rows for the table based on the data passed prepareRow // Prepare the row (this function needs to be called for each row before getting the row props) } = useTable({ columns, data }); /* Render the UI for your table - react-table doesn't have UI, it's headless. We just need to put the react-table props from the Hooks, and it will do its magic automatically */ 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> ); }
Above, we pass columns
and data
to useTable
. The useTable
Hook will return the necessary props for the table, body, and the transformed data to create the header and cells. The header will be generated by iterating through headerGroups
, and the table body’s rows will be generated by looping through rows
:
{rows.map((row, i) => { prepareRow(row); // This line is necessary to prepare the rows and get the row props from react-table dynamically // Each row can be rendered directly as a string using the react-table render method return ( <tr {...row.getRowProps()}> {row.cells.map(cell => { return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>; })} </tr> ); })}
You’ll also notice that the genre is an array, but it will be automatically converted to a comma-separated string in our final output.
If we run our application at this point, we should get the following output:
This table is adequate for most applications, but what if we require custom styles? With React Table, you can define custom styles for each cell; it is possible to define styles in the column
object, as shown below. For example, let’s make a badge-like custom component to display each genre:
// App.js import React, { useMemo } from "react"; ... // Custom component to render Genres const Genres = ({ values }) => { // Loop through the array and create a badge-like component instead of a comma-separated string return ( <> {values.map((genre, idx) => { return ( <span key={idx} className="badge"> {genre} </span> ); })} </> ); }; function App() { const columns = useMemo( () => [ ... { Header: "Details", columns: [ { Header: "Language", accessor: "show.language" }, { Header: "Genre(s)", accessor: "show.genres", // Cell method will provide the cell value; we pass it to render a custom component Cell: ({ cell: { value } }) => <Genres values={value} /> }, { Header: "Runtime", accessor: "show.runtime", // Cell method will provide the value of the cell; we can create a custom element for the Cell Cell: ({ cell: { value } }) => { const hour = Math.floor(value / 60); const min = Math.floor(value % 60); return ( <> {hour > 0 ? `${hour} hr${hour > 1 ? "s" : ""} ` : ""} {min > 0 ? `${min} min${min > 1 ? "s" : ""}` : ""} </> ); } }, { Header: "Status", accessor: "show.status" } ] } ], [] ); . . . } . . .
We updated the Genres
column above by iterating and sending its values to a custom component, creating a badge-like element. We also changed the runtime column to show the watch hour and minute based on the time. Following this step, our table UI should look like the following:
We’ve seen how we can customize the styles for each cell based on our needs; you can show any custom element for each cell based on the data value.
useFilters
Let’s add a bit more functionality to our table. The demo page for React Table already provides everything you need to create a custom smart table. Just one thing is missing in the demo, global search functionality. I decided to create that using the useFilters
plugin Hook from React Table.
First, let’s create a search input in Table.js
:
// Table.js // Create a state const [filterInput, setFilterInput] = useState(""); // Update the state when input changes const handleFilterChange = e => { const value = e.target.value || undefined; setFilterInput(value); }; // Input element <input value={filterInput} onChange={handleFilterChange} placeholder={"Search name"} />
It’s a straightforward, simple state to manage the input state. But, how can we pass this filter value to our table and filter the table rows?
For that, React Table has a nice Hook plugin called useFilters
:
// Table.js const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setFilter, // The useFilter Hook provides a way to set the filter } = useTable( { columns, data, }, useFilters // Adding the useFilters Hook to the table // You can add as many Hooks as you want. Check the documentation for details. You can even add custom Hooks for react-table here );
In our example, we’ll set the filter only for the Name
column. To filter the name, when the input value changes, we need to set our first param as the column accessor or ID value and our second param as the search filter value.
Let’s update our handleFilterChange
function:
const handleFilterChange = e => { const value = e.target.value || undefined; setFilter("show.name", value); // Update the show.name filter. Now our table will filter and show only the rows which have a matching value setFilterInput(value); };
After the search implementation, our UI will look like the following:
This is a very basic example for filters, and the React Table API provides several options. Be sure to check out the API documentation.
useSortBy
Let’s implement one more basic functionality for our table, sorting. Let’s allow sorting for all columns; it’s very simple and similar to filtering.
We need to add a plugin Hook called useSortBy
and create the styles to show the sorting icon in the table. It will automatically handle the sorting in ascending and descending orders:
// Table.js const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setFilter } = useTable( { columns, data }, useFilters, useSortBy // This plugin Hook will help to sort our table columns ); // Table header styling and props to allow sorting <th {...column.getHeaderProps(column.getSortByToggleProps())} className={ column.isSorted ? column.isSortedDesc ? "sort-desc" : "sort-asc" : "" } > {column.render("Header")} </th>
Based on the sorting, we add the class names sort-desc
or sort-asc
. We also add the sorting props to the column header:
{...column.getHeaderProps(column.getSortByToggleProps())}
This will automatically allow sorting for all columns. You can control that by disabling sorting in specific columns by using the disableSortBy
option on a column. In our example, we allowed sorting on all columns. Feel free to play around with the demo.
After our sorting implementation, the UI looks like the following:
Of course, you can extend this demo even further. Let me know if you need help in the comments section. Some ideas to extend it include:
setAllFilters
instead of setFilter
)sortby
for columnsTo extend this demo, check out React Table’s extensive example page. It has a very good kitchen sink to play around with, and it provides solutions for most use cases.
As mentioned at the beginning of this article, TanStack Table is a significant advancement to React Table, allowing the library to support even more JavaScript frameworks, including Svelte, Solid, and Vue. TanStack table includes a complete rewrite to TypeScript, improved support for server-side operations, and a vastly expanded and improved API, among other things.
Furthermore, due to its design, TanStack Table implementation is consistent across the many supported frameworks, meaning the Hooks necessary to create a table in React are the same Hooks used to create a table in a Svelte application. Therefore, it’s easier for people who are already familiar with React Table to use it in other frameworks.
As opposed to React Table v7, you can install TanStack Table with the code below:
npm install @tanstack/react-table
Then, we can import it into our application as follows:
import { useReactTable } from '@tanstack/react-table' function App() { const table = useReactTable(options) . . . }
The column configuration is the most notable difference between working with TanStack Table and React Table v7. In v8, the createColumnHelper
Hook adds a new method for creating columns. In the example below, we’ll recreate the column from our previous table example with TanStack Table v8:
import { createColumnHelper } from "@tanstack/react-table"; const columnHelper = createColumnHelper(); const columns = [ columnHelper.group({ id: "tv-show", header: "TV Show", columns: [ columnHelper.accessor("show.name", { header: () => "Name", }), columnHelper.accessor("show.type", { header: () => "Type", }), ], }), columnHelper.group({ header: "Details", columns: [ columnHelper.accessor("show.language", { header: () => "Language", }), columnHelper.accessor("show.genres", { header: () => "Genre(s)", }), ], // . . . }), ];
Furthermore, table options in v8 are mostly the same as in v7, with some larger differences centered around optional state management and data pipeline control. You can find more information on other implementations on the documentation page.
Although React Table is the most popular React table library, it’s not always the best solution for building tables in React. There are plenty of alternatives that might also suit your needs, depending on your particular project and use case.
Below are some alternative React table libraries that are worth checking out.
react-data-grid is used for creating smart tables. It has more than 5,000 GitHub stars at the time of writing and is well-maintained.
Use react-data-grid when your data table needs:
react-data-grid covers almost all the basic needs for a data table. However, it doesn’t support pagination by default, so if your table requires pagination, you need to manually implement and handle it. By default, react-data-grid supports longer table UIs and is optimized for performance, so pagination might not be necessary unless the UX demands it.
react-data-grid also uses Bootstrap for styling. You can still use react-data-grid without it, but then you’d have to add your own styling for the table. It’s not easily customizable compared to React Table, which allows you to create the table structure. In react-data-grid, the library creates the table UI, so it’s not great for UI-heavy custom pages.
While the above points aren’t exactly shortcomings, they’re nice to know about before you start using react-data-grid. Ultimately, react-data-grid is best when your application needs a mini editable data table with nice UX, similar to Google Sheets or Airtable.
react-datasheet is similar to react-data-grid. It has around 5,000 GitHub stars and is also a well-maintained library.
react-datasheet focuses primarily on helping you create your own Google Sheets-like application. It has basic features built in to create such UX-heavy applications. Once again, it might not be suitable for creating a general-purpose page UI with tables.
Unlike react-data-grid, however, react-datasheet is not optimized for large datasets, so use it for small applications that require sheets-like functionality. It has only this one use case, and its features are very limited compared to those of react-data-grid.
As the name implies, react-virtualized is heavily optimized for performance when the dataset is large. At the time of writing, it has over 24k stars on GitHub. This library is not exactly a table library; it does much more. It is used exclusively for displaying large datasets on the UI in different formats, like grid, table, and list.
When your dataset is very large, rendering performance is the key metric for the table; if that’s the case, go for react-virtualized. For normal use cases, this library would be overkill, and the API would be too advanced.
You should use react-virtualized for custom timelines, charts involving infinitely long calendars, and heavy UI elements for your large dataset.
For a simple page with limited data, custom styles, and minimum interactivity like sorting and filtering, use React Table. To build a mini Google Sheets-like application with limited data, use react-data-grid or react-datasheet. For a Google Sheets or Airtable-like application with a large dataset, use react-data-grid.
When you’re working with a very large dataset and you need a custom UI that offers tables, grids, and many more options, choose react-virtualized.
After we add sorting, this is how the final React Table demo looks. You can play around with the demo and check out the codebase for our React Table example.
In this article, we’ve learned how to build a table UI using React. It’s not difficult to create your own table for basic use cases, but make sure you’re not reinventing the wheel wherever possible. Hope you enjoyed learning about table UIs. Let me know about your experience with tables in the comments.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowToast notifications are messages that appear on the screen to provide feedback to users. When users interact with the user […]
Deno’s features and built-in TypeScript support make it appealing for developers seeking a secure and streamlined development experience.
It can be difficult to choose between types and interfaces in TypeScript, but in this post, you’ll learn which to use in specific use cases.
This tutorial demonstrates how to build, integrate, and customize a bottom navigation bar in a Flutter app.
13 Replies to "React Table: A complete guide with updates for TanStack Table"
“react–table is one of the most widely used table libraries in React.”… yet somehow I found two bugs in the first two minutes of playing with it – https://github.com/tannerlinsley/react-table/issues/created_by/dandv
I don’t know why we keep reinventing the wheel. To repeat the same mistakes? There have been open source table components for over 10 years now. And of course, there’s a much larger crop of React table libraries than the 3 covered in this article. Material-ui, Autodesk, Vaadin, Blueprint all have React-compatible tables, and https://github.com/brillout/awesome-react-components#table–data-grid lists a ton more.
“When to build your own table UI” should be “pretty much never”.
Thanks for the detailed tutorial; helped me out a lot!
Thanks for writing this article. In my current app, I use ag-Grid community edition and Tabulator. Both provide inline editing which is a must for me. Unfortunately ag-Grid community edition doesn’t provide row grouping and tree view which I sometimes need. Those features are only available in enterprise edition. So I use Tabulator : http://tabulator.info/, whenever I need row grouping and tree view. There is a React version of Tabulator.
However, I am now concerned with the bundle size. Both libraries are big, especially ag-Grid. So recently I have been searching other options. React-Data-Grid looked nice, but when I saw the size of my app after adding a module that used it, I was surprised : my app size increased by about 10Mb. I also tried Reac-Table which was satisfactory in terms of the bundle size only, but it’s unlikely I will use it in my current app. I need some experiment of it prior to using it in a real project. Besides, I still lack of skill in using Hooks.
I am quite tired searching for alternatives of ag-Grid community edition and Tabulator which have been serving me well apart of their bundle size.
I am still wondering what caused such a huge bundle size increase when I used React-Data-Grid. Maybe you know the reason.
Very nice article. Thank you Paramanantham. A couple small contributions: App.js is missing: import axios from ‘axios’; I also had problems with “yarn add” not installing things correctly. Fixed by switching to “npm install”. When I finally got the table to show up, there were no borders around the rows and columns like yours showed, nor was every other row shaded. Didn’t look as nice. Why?
For Alternating table colors
Add a table.css file
make import Table.css into Table.js
Add this code below
table {
border-collapse: collapse;
width: 100%;
}
th, td {
text-align: left;
padding: 8px;
}
tr:nth-child(even) {background-color: #f2f2f2;}
I am having the same issue with the lack of borders, also his code here:
const Genres = ({ values }) => {
// Loop through the array and create a badge-like component instead of a comma-separated string
return (
{values.map((genre, idx) => {
return (
{genre}
);
})}
);
};
Does not have proper syntax.
Well actually i was told to create a DataTable component, not to use an existing one, so for me it make sense that he share this knwoledge with us, though i agree with you there’re lot of libraries that we can use, but dependens on client also or that’s my particular case…
thank you for this awesome article. really help.
please , is there a way you can do vertical column header in react-table and also add pagination.
i am having issue connecting pagination with this table you build in this tutorial.
thank you.
how to show particular ID dta from react dattable in next page
Hey,
Can you please show how to implement setAllFilters([]). I do not want to use globalFilter, as I am looking to filter only two columns using the filter textfield provided in your example.
There is no example online that shows how to use setAllFilters instead of setFilter. Also, the important thing is I want to use the same filterInput for both columns.
Thanks !!
I am really glad that I read this and looking forward for more articles about expansion table. Thank you for sharing this it really helps a lot!
Did you find a way to do this? I’m having problems with the vertical column header thing
Anyone here had some issues trying to attach the API data to the table?