TanStack react table v8 style each cell based on the cell value

11.5k Views Asked by At

We are migrating our tables from v7 to v8. And I'm kinda got a problem with cells conditional styling. So basically what I want to do is, based on a status (which is coming to table data) I need to add a specific className to every cell in a row.

In v7 we used this: https://react-table-v7.tanstack.com/docs/examples/data-driven-classes-and-styles

But in v8 I can't find anything like that....

So far I tried to use meta in column definitions https://tanstack.com/table/v8/docs/api/core/column-def#meta where I can set some values to className property, and use it in my JSX like this:

className={cell.column.columnDef.meta?.className}

But problem is anything I can set to meta are static values. For my case I need to set specific className based on my status value. And seems like in meta we can't access any cell props...

const driverFormatter = ({ row }) => {
  const { status } = row.original;

  return <span>{status}</span>;
};

const columns: ColumnDef<any,any>[] = [
    {
      accessorKey: "customerName",
      header: "Customer"
    },
    {
      accessorKey: "driver",
      header: "Driver",
      enableSorting: false,
      cell: driverFormatter,
      meta: {
          className: "disabled",
     },
    },
    ...

So is there are any way of achieving that using v8???

Thank you!

3

There are 3 best solutions below

0
On

Tech Stack: Next.js v13 Javascript Tanstack v8

Hey I was stuck with same but I found an easy but costly fix: I will share my code try it out : Notes: using getValue() function to get value of every cell in table body, and that value is a arg to another function named getRolesCellClassName()

import AdminStyle from "../src/styles/admin.module.css";
import React, { useEffect, useState, useMemo } from "react";
import {
  useReactTable,
  getCoreRowModel,
  flexRender,
} from "@tanstack/react-table";

const BasicTable = () => {
 const [users, setUsers] = useState([]);
 const fetchUsers = async () => {
  const response = await fetch("http://localhost:9002/user").catch((err) =>
   console.log(err)
 );
if (response) {
  const usersData = await response.json();
   const transformedUsers = usersData.flatMap(function(us) {
     return us.roles.map(function(role) {
       return { ...us, roles: role };
     });
   });
   console.log(usersData);
   console.log(transformedUsers);
   setUsers(transformedUsers);
 }
};

useEffect(() => {
  fetchUsers();
}, []);

const data = useMemo(() => users, [users]);

 //  =========================Returning dynamic style==========================
 const getRolesCellClassName = (value) => {
  console.log(value)
  if (value === "admin") {
    return `${AdminStyle.adminRoleBadge} bg-green-500 text-white`;
  } else if (value === "user") {
    return `${AdminStyle.userRole} bg-blue-500 text-white`;
  }
  // Add more conditions for other roles if needed

  return ""; // Default CSS class if no specific condition is met
 };


 // /**@type import('@tanstack/react-table').ColumnDef<any> */
 const columns = [

 {
  header: "Name",
  accessorFn: (row) => `${row.first_name} ${row.last_name}`,
  // Adds classes to this particular column but not to header of that column
  columnClassName: "font-medium text-sm  "
},
{
  header: "Email",
  accessorKey: "email",
  // Adds classes to this particular column but not headers
  columnClassName: AdminStyle.emailColumn
},
{
  header: "Roles",
  accessorKey: "roles",
  cellClassName: (cell) => getRolesCellClassName(cell.value), // Apply the cell class dynamically
},

  {
    header: "Actions",
    cell: (row) => {
      return (
        <button 
        onClick={(row)=>{alert('message' + row.getValue.email)}}
        >
          Edit
          {row.getValue()}
        </button>
      );
    },
    accessorKey: "actions",
  },
];

const table = useReactTable({
   data,
   columns,
  getCoreRowModel: getCoreRowModel(),
  });

 return (
  <table className="w-full ">
   <thead className={AdminStyle.borderBtm}>
    {table.getHeaderGroups().map((headerGroup) => (
      <tr key={headerGroup.id} >
        {headerGroup.headers.map((header) => (
          <th
            key={header.id}
            className={`${AdminStyle}  border-btm text-left gap-3 px-6 py-2 `}
          >
            {flexRender(
              header.column.columnDef.header,
              header.getContext()
            )}
          </th>
        ))}
      </tr>
    ))}
  </thead>

  <tbody>
    {table.getRowModel().rows.map((row) => (
      <tr
        key={row.id}
        className={` ${AdminStyle.borderBtm} w-full table-hea border-btm  box-border items-center`}
      >
        {row.getVisibleCells().map((cell) => (
          <td
            key={cell.id}
            className={`${AdminStyle} pl-6  gap-3 py-6 ${cell.column.columnDef.columnClassName} `}
          >
            {/* To get the actual value 
            {console.log(cell.getValue())} */}

            {/* ================================Conditionally add 
styles=============================== */}
            <span className={`${getRolesCellClassName(cell.getValue())}`}>
            {flexRender(cell.column.columnDef.cell, cell.getContext())}
            </span>
          </td>
        ))}
      </tr>
    ))}
  </tbody>
    </table>
  );
};

export default BasicTable;
1
On

I also encountered the same problem and as you mentioned there is separate example in V7 .

and for v8 there is not enough documentation or correct example

Here is link to my implementation where I was able to add different column color in v8. Hope this helps. If you any further doubt let me know

1
On

First Method - Through column definition meta key:

We can get the detailed context of each cell in V8 through meta, like

{
    accessorKey: 'section',
    cell: (info) => info.getValue(),
    footer: (props) => props.column.id,
    meta: {
        getCellContext: (context: CellContext<Person, unknown>) => {
            if (context.row.index === 0) {
                // console.log(context)
                return {
                    style: { fontWeight: 'bold', minWidth: '30%', textTransform: 'uppercase' },
                    className: 'bold',
                }
            }
        },
    },
},

To get dynamic context like this, we need to include additional props to ColumnMeta.

declare module '@tanstack/react-table' {
    interface ColumnMeta<TData, TValue> {
        // Your additional properties here
        getCellContext: (context: CellContext<TData, TValue>) => TableCellProps | void
    }
}

Then we can use this func inside the loop

<TableBody>
    {table
        .getRowModel()
        .rows.slice(0, 20)
        .map((row) => {
            return (
                <TableRow key={row.id} {...getRowProps(row)}>
                    {(isSplit ? row.getCenterVisibleCells() : row.getVisibleCells()).map((cell) => {
                        let hasMeta = cell.getContext().cell.column.columnDef.meta

                        return (
                            <TableCell
                                key={cell.id}
                                {...(hasMeta && { ...hasMeta.getCellContext(cell.getContext()) })}
                            >
                                {flexRender(cell.column.columnDef.cell, cell.getContext())}
                            </TableCell>
                        )
                    })}
                </TableRow>
            )
        })}
</TableBody>

By this, you can add styles, className,, etc. To each *** TABLE CELL*** based on the value.

But you can't style the TABLE ROW with the above method. For that

Second Method - Through callback function:

Define two functions for rows and cells

// Func to provide props to table row
const getRowProps = (context: Row<Person>): TableRowProps | void => {
    console.log(context)
    if (context.original.section.includes('section')) {
        return { className: 'sectionHeader', sx: (theme) => ({ background: theme.palette.primary.light }) }
    }
}

// Func to provide props to table cell
const getCellProps = (context: CellContext<Person, unknown>): TableCellProps | void => {
    if (context.row.index === 0) {
        // console.log(context)
        return {
            style: { fontWeight: 'bold', minWidth: '30%', textTransform: 'uppercase' },
            className: 'bold',
        }
    }
}

And just destructure the functions

<TableBody>
    {table
        .getRowModel()
        .rows.slice(0, 20)
        .map((row) => {
            return (
                <TableRow key={row.id} {...getRowProps(row)}>  //<- here
                    {row.getLeftVisibleCells().map((cell) => {

                        return (
                            <TableCell
                                key={cell.id}
                                {...getCellProps(cell.getContext())} // <-here
                            >
                                {flexRender(cell.column.columnDef.cell, cell.getContext())}
                            </TableCell>
                        )
                    })}
                </TableRow>
            )
        })}
</TableBody>