DEV Community

Cover image for Ultimate TanStack React Table Guide: Tips & Best Practices
Md. Maruf Rahman
Md. Maruf Rahman

Posted on • Edited on • Originally published at practicaldev.online

Ultimate TanStack React Table Guide: Tips & Best Practices

Building data tables in React is one of those tasks that seems simple until you actually try to do it right. I've built tables from scratch multiple times, and each time I'd implement sorting, then filtering, then pagination, then realize I need row selection, column resizing, and virtualization for large datasets. That's when I discovered TanStack Table (formerly React Table), and it changed everything.

TanStack Table is a headless table library, which means it handles all the logic (sorting, filtering, pagination, etc.) but doesn't impose any styling. You get full control over how your table looks while benefiting from battle-tested table logic. It's perfect for building custom table components that match your design system.

What is TanStack Table?

TanStack Table (formerly React Table) is a headless UI library for building powerful data tables in React. It provides:

  • Sorting - Click column headers to sort data
  • Filtering - Global and column-specific filters
  • Pagination - Navigate through pages of data
  • Row Selection - Select single or multiple rows
  • Column Resizing - Resize columns dynamically
  • TypeScript Support - Full type safety out of the box
  • Headless Design - Complete control over UI rendering

Installation

Install TanStack Table:

npm install @tanstack/react-table
Enter fullscreen mode Exit fullscreen mode

For TypeScript projects, types are included automatically.

Basic Table Setup

Here's a basic table component with sorting, filtering, and pagination:

import { useState } from "react";
import {
  useReactTable,
  getCoreRowModel,
  getFilteredRowModel,
  getSortedRowModel,
  getPaginationRowModel,
  flexRender,
  type ColumnDef,
} from "@tanstack/react-table";

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
}

const columns: ColumnDef<Product>[] = [
  {
    accessorKey: "name",
    header: "Product Name",
    enableSorting: true,
  },
  {
    accessorKey: "price",
    header: "Price",
    enableSorting: true,
  },
  {
    accessorKey: "stock",
    header: "Stock",
    enableSorting: true,
  },
];

function ProductsTable({ data }: { data: Product[] }) {
  const [globalFilter, setGlobalFilter] = useState("");
  const [pagination, setPagination] = useState({
    pageIndex: 0,
    pageSize: 10,
  });

  const table = useReactTable({
    data,
    columns,
    state: {
      globalFilter,
      pagination,
    },
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onGlobalFilterChange: setGlobalFilter,
    onPaginationChange: setPagination,
  });

  return (
    <div>
      <input
        type="text"
        value={globalFilter}
        onChange={(e) => setGlobalFilter(e.target.value)}
        placeholder="Search..."
      />
      <table>
        <thead>
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th key={header.id}>
                  {flexRender(header.column.columnDef.header, header.getContext())}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {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>
        <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
          Previous
        </button>
        <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
          Next
        </button>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use TypeScript for type safety
  2. Implement proper loading and error states
  3. Add global and column-specific filters
  4. Handle pagination properly
  5. Enable row selection when needed
  6. Style tables to match your design system

πŸ“– Read the Complete Guide

This is just a brief overview! The complete guide on my blog includes:

  • βœ… Complete Table Component - Full implementation with all features
  • βœ… Row Selection - Single and multiple row selection
  • βœ… Column Filtering - Advanced filtering patterns
  • βœ… Server-Side Pagination - Handling large datasets
  • βœ… Column Resizing - Dynamic column sizing
  • βœ… Real-world examples from production applications

πŸ‘‰ Read the full article with all code examples here


What's your experience with TanStack Table? Share your tips in the comments! πŸš€

For more React guides, check out my blog covering React Router, TypeScript, React Hook Form, and more.

Top comments (0)