DEV Community

Cover image for TanStack Table - Quick Learning
Abdul Ahad Abeer
Abdul Ahad Abeer

Posted on • Originally published at abeer.hashnode.dev

TanStack Table - Quick Learning

Basic React Table

Create an instance of a react-table.

const BasicTable = () => {
    ......
    ...

    const table = useReactTable({ data, columns })

    return (
        <div></div>
    )
}
Enter fullscreen mode Exit fullscreen mode

data is the data the table contains. Columns need to be defined based on what data the table is going to contain. The following is how you define the columns.

const columns = [
    {
      header: "ID",
      accessorKey: "id",
      footer: "ID",
    },
    {
      header: "First Name",
      accessorKey: "first_name",
      footer: "First Name",
    },
    {
      header: "Last Name",
      accessorKey: "last_name",
      footer: "Last Name",
    },
    ......
    ...
]
Enter fullscreen mode Exit fullscreen mode

what each property does:

  • header: This defines the column's title that appears at the top of the table. It's what users see as the column name.

  • accessorKey: This is the key used to extract data from each row's object. It tells the table which property from your dataset should be displayed in this column. So, accessorKey should match the exact property name in your data objects.

  • footer: Similar to header, but it defines the text that appears at the bottom of the column as a footer.

Render table in jsx:

<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>
    ...
  </tbody>
  <tfoot></tfoot>
</table>
Enter fullscreen mode Exit fullscreen mode

Why Are There headerGroups? Normally, when you define simple columns, there’s only one header row. However, if you group columns under a parent column, React Table creates multiple header rows.

What Does header.getContext() Do Here? header.getContext() creates an object that contains useful information about the column and table state. This context object is then passed into flexRender(), allowing it to properly render the header.

Why is header.getContext() Needed?

  • In react-table, header.column.columnDef.header can be:

    1. A string (e.g., "Name" -> just displayed as-is).
    2. A function (e.g., ({ column }) => <span>{column.id} Column</span> -> needs context to work).
  • If header is a function, it requires an argument (getContext() provides this argument).

  • flexRender() takes this function and calls it with the necessary context.

Render Rows and Cells

<table>
   <thead>
       ...
   </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>
   <tfoot>
   </tfoot>
</table>
Enter fullscreen mode Exit fullscreen mode

This is how you can render the data. But data will be rendered only when getCoreRowModel is set in table instance:

// import { getCoreRowModel } from "@tanstack/react-table"

const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
});
Enter fullscreen mode Exit fullscreen mode

Set Footer

<tfoot>
  {table.getFooterGroups().map((footerGroup) => (
    <tr key={footerGroup.id}>
      {footerGroup.headers.map((header) => (
        <th key={header.id}>
          {flexRender(header.column.columnDef.footer, header.getContext())}
        </th>
      ))}
    </tr>
  ))}
</tfoot>
Enter fullscreen mode Exit fullscreen mode

Custom Cell Rendering

const columns = [
    ......
    ...
    {
      header: "Date of Birth",
      accessorKey: "dob",
      footer: "Date of Birth",
      cell: (info) =>
        DateTime.fromISO(info.getValue()).toLocaleString(DateTime.DATE_MED),
    },
  ]
Enter fullscreen mode Exit fullscreen mode

Basically, this is how you can customize cells.

The function parameter is the object where you can have all the information regarding the particular cell.

Accessor Function - accessorFn

accessorKey is a static value property. But instead of static value, we can have dynamic value with accessorFn

const columns = [
  {
    header: "ID",
    accessorKey: "id",
    footer: "ID",
  },
  {
    header: "Name",
    accessorFn: (row) => `${row.first_name} ${row.last_name}`, // accessor function
    footer: "Name",
  },
  // {
  //   header: "First Name",
  //   accessorKey: "first_name",
  //   footer: "First Name",
  // },
  // {
  //   header: "Last Name",
  //   accessorKey: "last_name",
  //   footer: "Last Name",
  // },
]
Enter fullscreen mode Exit fullscreen mode

Header Groups with Child Columns

const columns = [
  {
    header: "ID",
    accessorKey: "id",
    footer: "ID",
  },
  {
    header: "Name",
    columns: [
      {
        header: "First",
        accessorKey: "first_name",
        footer: "First Name",
      },
      {
        header: "Last",
        accessorKey: "last_name",
        footer: "Last Name",
      },
    ],
  },
  ......
  ...
];
Enter fullscreen mode Exit fullscreen mode

Here, this creates 2 layer of header groups. This creates gibberish like the first group is going to have ID and Name. The second group is also going to have ID and then First and Last. This way it doesn’t serve our purpose. So, render them this way:

<thead>
  {table.getHeaderGroups().map((headerGroup) => (
    <tr key={headerGroup.id}>
      {headerGroup.headers.map((header) => (
        <th key={header.id}>
          {header.isPlaceholder
            ? null
            : flexRender(header.column.columnDef.header, header.getContext())}
        </th>
      ))}
    </tr>
  ))}
</thead>;

/*
Parent HeaderGroup > -- Name
Child_ HeaderGroup > ID First Last
*/
Enter fullscreen mode Exit fullscreen mode

Name header has child columns. These child columns merge with headers that don’t have child columns.

So, there will be placeholders in the first header group. The placeholders will have null values as it’s in the code.

Pagination

In order to have pagination, the first condition you have to set getPaginationRowModel in the useReactTable hook’s object.

const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
});
Enter fullscreen mode Exit fullscreen mode

Now, add pagination buttons right after the table elements:

<table>
......
...
</table>
<div>
  <button onClick={() => table.setPageIndex(0)}>First page</button>
  <button onClick={() => table.previousPage()}>Previous page</button>
  <button onClick={() => table.nextPage()}>Next page</button>
  <button onClick={() => table.setPageIndex(table.getPageCount() - 1)}>
    Last page
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

These button events are pretty self-explanatory.

This way it creates a conflict. The conflict is when there are no data, it will still try to get the next data and obviously it will show no data or empty. So, disabling the buttons in such cases is the solution.

<div>
  <button onClick={() => table.setPageIndex(0)}>First page</button>
  <button
    disabled={!table.getCanPreviousPage()}
    onClick={() => table.previousPage()}
  >
    Previous page
  </button>
  <button disabled={!table.getCanNextPage()} onClick={() => table.nextPage()}>
    Next page
  </button>
  <button onClick={() => table.setPageIndex(table.getPageCount() - 1)}>
    Last page
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

These methods are also very much self-explanatory.

Sorting

First of all set getSortedRowModel.

Now, set the sorting state (state property after getSortedRowModel).

Then, set ascending and descending sign right after every header value.

Then, add onClick={header.column.getToggleSortingHandler()} in header to change sorting.

const [sorting, setSorting] = useState([]);

const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getSortedRowModel: getSortedRowModel(),
  state: {
    sorting: sorting,
  },
  onSortingChange: setSorting,
});

return (
  <div>
    <table>
      <thead>
        {table.getHeaderGroups().map((headerGroup) => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <th
                key={header.id}
                onClick={header.column.getToggleSortingHandler()}
              >
                {header.isPlaceholder ? null : (
                  <div>
                    {flexRender(
                      header.column.columnDef.header,
                      header.getContext()
                    )}
                    {
                      { asc: "↑", desc: "↓" }[
                        header.column.getIsSorted() ?? null
                      ]
                    }
                  </div>
                )}
              </th>
            ))}
          </tr>
        ))}
      </thead>
    </table>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

TanStack Table uses ‘asc’ and ‘desc’ by default for sorting. So, the sorting mechanism here is pretty straight-forward.

Filtering

set getFilteredRowModel(),

const table = useReactTable({
  ......
  ...,
  getFilteredRowModel: getFilteredRowModel(),
});
Enter fullscreen mode Exit fullscreen mode

Here, we need to set filtering state as well.

const [filtering, setFiltering] = useState('')

const table = useReactTable({
  ......
  ...,
  state: {
      ...,
      globalFilter: filtering
  },
  onGlobalFiltering: setFiltering
});
Enter fullscreen mode Exit fullscreen mode

This globalFilter setting makes a searching mechanism for data table.

Now, add a search input right before the table:

<>
  <input
    type="text"
    value={filtering}
    onChange={(e) => setFiltering(e.target.value)}
  />
  <table></table>
</>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)