Editor’s note: This article was last updated by Hussain Arif on 16 September 2024 to incorporate additional search functionalities such as global filtering, column searching, and column resizing, and to use properties like getFilteredRowModel
, getSortedRowModel
, and getGroupedRowModel
.
TanStack Table, formerly known as React Table, is one of the most widely used table libraries in React. React Table offers all the basic features necessary for a simple table while remaining lightweight. And because it is headless, it can’t render or supply UI elements. Therefore, you’re responsible for designing your table using the state and callback of the React Table API.
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 before, 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, and filtering. 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 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.
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 for displaying 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 Vite:
# for npm 7+, an extra '--' is needed! npm create vite@latest react-table-demo -- --template react-ts cd react-table-demo npm install #now install all packages
Calling an API with Axios
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
Next, replace the contents of the src/App.tsx
file with the code below:
//src/App.tsx import { useEffect, useState } from "react"; import axios from "axios"; import "./App.css"; function App() { const [data, setData] = useState(); const fetchData = async () => { const result = await axios("https://api.tvmaze.com/search/shows?q=snow"); setData(result.data); }; useEffect(() => { fetchData(); }, []); return <></>; } export default App;
Above, we created a state called data
. Once the component gets mounted, we fetch movie content from the TVMAZE API using Axios and saves the returned result in the data
state variable.
This API returns the following data:
// the API gives an array of TV Shows. Here is one item: // we will later use this response to create a TypeScript object.. //that will help us model the Show interface [ { "score": 0.86069167, "show": { "id": 10412, "url": "...", "name": "Snow", "type": "Scripted", "language": "English", "genres": [ "Comedy" ], "status": "...", "runtime": 120, "averageRuntime": 120, "premiered": "...", "ended": "...", "officialSite": "..", "schedule": { "time": "..", "days": [ ".." ] }, "rating": { "average": null }, "weight": 40, "network": { "id": 26, "name": "..", "country": { "name": "..", "code": "..", "timezone": "..." }, "officialSite": ".." }, "webChannel": null, "dvdCountry": null, "externals": { "tvrage": null, "thetvdb": null, "imdb": null }, "image": { "medium": "..." }, "summary": "...", "updated": 1670595447, "_links": { "self": { "href": ".." }, "previousepisode": { "href": "...", "name": "..." } } } }, //other TV shows.. ]
Add React Table with the command below:
npm install @tanstack/react-table
React Table uses React Hooks. It has a main table hook called useReactTable
, 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 useReactTable
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
will be an object that configures our table columns.
In the /src
folder, create a new Table.tsx
file and paste the following code:
// src/Table.tsx //create a Show object for TypeScript(see API response above for reference): //only these properties are relevant to us: export type Show = { show: { status: string; name: string; type: string; language: string; genres: string[]; runtime: number; }; }; //now create types for props for this Table component(https://tanstack.com/table/latest/docs/framework/react/examples/sub-components) type TableProps<TData> = { data: TData[]; columns: GroupColumnDef<TData>[]; }; export default function Table({ columns, data }:TableProps<Show>) { // Table component logic and UI come here return <></>; }
Let’s modify the content in App.tsx
to include the columns for our table and also render the Table
component:
// src/App.tsx import { useEffect, useMemo, useState } from "react"; import axios from "axios"; import { createColumnHelper } from "@tanstack/react-table"; import Table, { Show } from "./Table"; function App() { const [data, setData] = useState<Show[]>(); const columnHelper = createColumnHelper<Show>(); //define our table headers and data const columns = useMemo( () => [ //create a header group: columnHelper.group({ id: "tv_show", header: () => <span>TV Show</span>, //now define all columns within this group columns: [ columnHelper.accessor("show.name", { header: "Name", cell: (info) => info.getValue(), }), columnHelper.accessor("show.type", { header: "Type", cell: (info) => info.getValue(), }), ], }), //create another group: columnHelper.group({ id: "details", header: () => <span> Details</span>, columns: [ columnHelper.accessor("show.language", { header: "Language", cell: (info) => info.getValue(), }), columnHelper.accessor("show.genres", { header: "Genres", cell: (info) => info.getValue(), }), columnHelper.accessor("show.runtime", { header: "Runtime", cell: (info) => info.getValue(), }), columnHelper.accessor("show.status", { header: "Status", cell: (info) => info.getValue(), }), ], }), ], [], ); const fetchData = async () => { const result = await axios("https://api.tvmaze.com/search/shows?q=snow"); setData(result.data); }; useEffect(() => { fetchData(); }, []); return <>{data && <Table columns={columns} data={data} />}</>; } 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
.
Now, let’s finish our Table
component:
// src/Table.tsx //extra code removed for brevity.. import { flexRender, getCoreRowModel, GroupColumnDef, useReactTable, } from "@tanstack/react-table"; export default function Table({ columns, data }: TableProps<Show>) { //use the useReact table Hook to build our table: const table = useReactTable({ //pass in our data data, columns, getCoreRowModel: getCoreRowModel(), }); // Table component logic and UI come here return ( <div> <table> <thead> {/*use the getHeaderGRoup function to render headers:*/} {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} </th> ))} </tr> ))} </thead> <tbody> {/*Now render the cells*/} {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> </div> ); }
Above, we passed columns
and data
to useReactTable
. The useReactTable
Hook will return the necessary props for the table, body, and the transformed data to create the header and cells. React will then generate the header by iterating through the headers using the getHeaderGroups
function, and the table body’s rows will be generated via the getRowModel()
function.
You’ll also notice that the genre
field is an array, but React will render it to a comma-separated string in our final output.
If we run our application at this point, we should get the following output:
While this table is adequate for most applications, 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:
// src/App.tsx //extra code removed for brevity type GenreProps = { genres: string[]; }; const Genres = ({ genres }: GenreProps) => { // Loop through the array and create a badge-like component instead of a comma-separated string return ( <> {genres.map((genre, idx) => { return ( <span key={idx} style={{ backgroundColor: "green", marginRight: 4, padding: 3, borderRadius: 5, }} > {genre} </span> ); })} </> ); }; //this function will convert runtime(minutes) into hours and minutes function convertToHoursAndMinutes(runtime: number) { const hour = Math.floor(runtime / 60); const min = Math.floor(runtime % 60); return `${hour} hour(s) and ${min} minute(s)`; } function App() { const columns = useMemo( () => [ //... columnHelper.group({ //.. columns: [ , //... columnHelper.accessor("show.genres", { header: "Genres", //render the Genres component here: cell: (info) => <Genres genres={info.getValue()} />, }), columnHelper.accessor("show.runtime", { header: "Runtime", //use our convertToHoursAndMinutes function to render the runtime of the show cell: (info) => convertToHoursAndMinutes(info.getValue()), }), //... ], }), ], [], ); //further code.. } //...
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:
As you can see, React Table has successfully styled our table with relative ease! If you need more help in rendering custom cells, refer to the documentation.
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.
getFilteredRowModel
Using the guide for global filtering, we can extend our table by adding global search capabilities. The getFilteredRowModel
property in the useReactTable
Hook will let React Table know that we want to implement filtering in our project.
First, let’s create a search input in Table.tsx
:
// src/Table.tsx //Create a searchbar: function Searchbar({ value: initialValue, onChange, ...props }: { value: string | number; onChange: (value: string | number) => void; } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange">) { const [value, setValue] = useState(initialValue); useEffect(() => { setValue(initialValue); }, [initialValue]); //if the entered value changes, run the onChange handler once again. useEffect(() => { onChange(value); }, [value]); //render the basic searchbar: return ( <input {...props} value={value} onChange={(e) => setValue(e.target.value)} /> ); } export default function Table({ columns, data }: TableProps<Show>) { const [globalFilter, setGlobalFilter] = useState(""); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), filterFns: {}, state: { globalFilter, //specify our global filter here }, onGlobalFilterChange: setGlobalFilter, //if the filter changes, change the hook value globalFilterFn: "includesString", //type of filtering getFilteredRowModel: getFilteredRowModel(), //row model to filter the table }); return ( <div> {/*Render the searchbar:*/} <Searchbar value={globalFilter ?? ""} onChange={(value) => setGlobalFilter(String(value))} placeholder="Search all columns..." /> {/*Further code..*/} </div> ); }
Let’s break this code down:
Searchbar
component. As the name suggests, this component will send user input to React Table. The library will then filter the table rows to match the user inputgetFilteredRowModel
method and passed our globalFilter
Hook to the state
property in the useReactTable
HookSearchBar
componentWhen run, this will be the result:
Thanks to React Table, column searching is also available to help the user filter out a specific column.
The code snippet below introduces column searching functionality in our app:
import { ColumnFiltersState } from "@tanstack/react-table"; export default function Table({ columns, data }: TableProps<Show>) { const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); const table = useReactTable({ //.... state: { columnFilters, globalFilter, }, onColumnFiltersChange: setColumnFilters, //... }); // Table component logic and UI come here return ( <div className="p-2"> {/*...further code..*/} <table> <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} <div> {/*If the column can be filtered, render the Filter component.*/} {header.column.getCanFilter() ? ( <div> <Filter column={header.column} /> </div> ) : null} </div> </th> ))} </tr> ))} </thead> {/*further code..*/} </table> <div className="h-4" /> </div> ); } //create a Filter component to use for column searching: function Filter({ column }: { column: Column<Show, unknown> }) { const columnFilterValue = column.getFilterValue(); return ( <Searchbar onChange={(value) => { column.setFilterValue(value); }} placeholder={`Search...`} type="text" value={(columnFilterValue ?? "") as string} /> ); }
If you notice, the code above is similar to that of its global counterpart. The only difference is that we’re rendering a Filter
component for those columns, which can be filtered.
This will be the result:
These are very basic example for filters, and the React Table API provides several options. Be sure to check out the API documentation for more information.
getSortedRowModel
Let’s implement one more basic functionality for our table: sorting. React Table allows sorting via the getSortedRowModel
method:
// src/Table.tsx import { SortingState, getSortedRowModel } from "@tanstack/react-table"; const [sorting, setSorting] = useState<SortingState>([]); const table = useReactTable({ //... getSortedRowModel: getSortedRowModel(), onSortingChange: setSorting, state: { sorting, }, //... }); return ( <div> {/*Extra code to render table and logic..(removed for brevity)*/} <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => { return ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( <div //when clicked, check if it can be sorted //if it can, then sort this column onClick={header.column.getToggleSortingHandler()} title={ header.column.getCanSort() ? header.column.getNextSortingOrder() === "asc" ? "Sort ascending" : header.column.getNextSortingOrder() === "desc" ? "Sort descending" : "Clear sort" : undefined } > {flexRender( header.column.columnDef.header, header.getContext(), )} {{ //display a relevant icon for sorting order: asc: " 🔼", desc: " 🔽", }[header.column.getIsSorted() as string] ?? null} </div> )} </th> ); })} </tr> ))} </thead> </div> );
Here, we passed in our getSortedRowModel
and onSortingChange
properties to inform the library that we want to add sorting to our project.
After our sorting implementation, the UI looks like the following:
As you can see, the user can now click to enable sorting for any column. You can disable the sorting functionality for certain columns via the enableSorting
flag:
//column related data in src/App.tsx: columns: [ columnHelper.accessor("show.name", { header: "Name", cell: (info) => info.getValue(), enableSorting: false, //disable sorting for this one }), columnHelper.accessor("show.type", { header: "Type", cell: (info) => info.getValue(), }), ] //...
getGroupedRowModel
We can even add a grouping feature using the getGroupedRowModel
method. This is great for cases where users want to group columns according to a certain category.
To start, first add the aggregationFn
property to the show.language
, show.name
, and show.type
columns:
//src/App.tsx const columns = useMemo( () => [ columnHelper.group({ //... columns: [ columnHelper.accessor("show.name", { //... aggregationFn: "count", }), columnHelper.accessor("show.type", { //... aggregationFn: "count", }), ], }), columnHelper.group({ //... columns: [ columnHelper.accessor("show.language", { //... aggregationFn: "count", }), ], }), ], //extra code removed for brevity.. [], );
Here, we’re setting the aggregationFn
property to count
. This tells React to just use count-based aggregation for those columns.
Next, make these changes to the Table.tsx
component:
//src/Table.tsx import { GroupingState, getGroupedRowModel, getExpandedRowModel } from "@tanstack/react-table"; const [grouping, setGrouping] = useState<GroupingState>([]); const table = useReactTable({ //.. getExpandedRowModel: getExpandedRowModel(), getGroupedRowModel: getGroupedRowModel(), onGroupingChange: setGrouping, state: { //.. grouping, }, }); return ( <div> {/*Further code..*/} <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => { return ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( <div> {header.column.getCanGroup() ? ( // If the header can be grouped, let's add a toggle <button {...{ onClick: header.column.getToggleGroupingHandler(), style: { cursor: "pointer", }, }} > {header.column.getIsGrouped() ? `(grouped): ` : `(ungrouped):`} </button> ) : null}{" "} {flexRender( header.column.columnDef.header, header.getContext(), )} </div> )} </th> ); })} </tr> ))} </thead> {/*Further code..*/} </div> );
Here’s what’s happening in this code block:
getExpandedRowModel
and getGroupedRowModel
properties to help us use sorting in our projectbutton
for every column. When clicked, it will trigger the header.column.getToggleGroupingHandler()
function. This will toggle grouping for the selected columnheader.column.getIsGrouped()
methodThis will be the result:
Tanstack Table also provides a ColumnSizing
API to help users resize table columns. This is great for situations where a certain row has to be expanded to make their table use up extra available width on the screen.
This code block demonstrates how to implement resizing functionality:
//src/Table.tsx const [columnResizeMode, setColumnResizeMode] = React.useState<ColumnResizeMode>("onChange"); const [columnResizeDirection, setColumnResizeDirection] = React.useState<ColumnResizeDirection>("ltr"); const table = useReactTable({ data, columns, columnResizeMode, //specify that we'll use resizing in this table columnResizeDirection, getCoreRowModel: getCoreRowModel(), debugTable: true, debugHeaders: true, debugColumns: true, }); return ( <div style={{ direction: table.options.columnResizeDirection }}> <table {...{ style: { width: table.getCenterTotalSize(), }, }} > <thead> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th {...{ key: header.id, colSpan: header.colSpan, style: { width: header.getSize(), }, }} > {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} <div {...{ onDoubleClick: () => header.column.resetSize(), //when held down, enable resizing functionality. onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler(), className: `resizer ${ table.options.columnResizeDirection } ${header.column.getIsResizing() ? "isResizing" : ""}`, style: { //keep on increasing/decreasing the column till resize mode is finished. transform: columnResizeMode === "onEnd" && header.column.getIsResizing() ? `translateX(${ (table.options.columnResizeDirection === "rtl" ? -1 : 1) * (table.getState().columnSizingInfo.deltaOffset ?? 0) }px)` : "", }, }} /> </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td {...{ key: cell.id, style: { //set the width of this column width: cell.column.getSize(), }, }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> </div> );
The explanation of the code is in the comments.
Let’s test it out! This will be the output:
In legacy codebases, developers often have to deal with older package versions. In this portion of the article, you will briefly learn how to migrate from an old React Table package to a more recent version, i.e., TanStack Table.
To start with, change your package names:
npm uninstall react-table @types/react-table # latest package is in the '@tanstack' scope. #Typescript is included by default npm install @tanstack/react-table
Next, update your imports:
import { useTable } from 'react-table' //old import { useReactTable } from '@tanstack/react-table' //new
Furthermore, update your useTable
Hook calls to useReactTable
like so:
//old import { useTable, usePagination, useSortBy } from "react-table"; const tableInstance = useTable( { columns, data }, useSortBy, usePagination, //order of hooks used to matter // etc. ); //new import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowModel, } from "@tanstack/react-table"; const tableInstance = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), //order doesn't matter anymore! // etc. });
Other than that, as of Tanstack Table v8, the way to declare columns has changed:
//old const columns = [ { accessor: "firstName", Header: "First Name", }, { accessor: (row) => row.lastName, Header: () => <span>Last Name</span>, }, // new: //the 'accessor' property has changed to accessorKey or accessorFn columnHelper.accessor("firstName", { //accessorKey header: "First Name", }), columnHelper.accessor((row) => row.lastName, { //accessorFn header: () => <span>Last Name</span>, }), // using accessorKey { accessorKey: "firstName", header: "First Name", }, //using accessorFn { accessorFn: (row) => row.lastName, header: () => <span>Last Name</span>, }, ];
These were the main action items needed to migrate to v8. The Tanstack documentation covers the migration process in great detail.
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. 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.
As the name implies, react-virtualized is heavily optimized for performance when the dataset is large. 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, opt 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. Read more about it in our comprehensive guide to rendering large lists with React Virtualized.
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, opt for react-virtualized.
In this article, we learned how to build a table UI using React and TanStack Table. It’s not difficult to create your own table for basic use cases, but make sure you’re not reinventing the wheel wherever possible.
I hope you enjoyed learning about table UIs. Let me know about your experience with tables in the comments below.
You can play around with the demo and check out the codebase for our React Table example.
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>
Would you be interested in joining LogRocket's developer community?
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 nowValidating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.
Learn how Remix enhances SSR performance, simplifies data fetching, and improves SEO compared to client-heavy React apps.
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?