reactjstanstack

Tanstack table header function


I am building a table using the Tanstack Table v.8. I've added the expanding rows functionality but I stumbled when trying to implement a toggle button which expands/collapses all rows. I can't figure out why the column header doesn't render a function, as in the tanstack example here: https://tanstack.com/table/v8/docs/examples/react/expanding. it doesn't even render a simple test function like () => <div>foo</div>.

I'm a beginner with JS/React so any help would be greatly appreciated.

"use client";
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  getExpandedRowModel,
  useReactTable,
  createColumnHelper,
} from "@tanstack/react-table";

import Image from "next/image";
import { useState, useMemo, useRef } from "react";
import useSWR from "swr";
import JobsFilterBox from "./JobsFilterBox";

const fetcher = (...args) => fetch(...args).then((res) => res.json());
const columnHelper = createColumnHelper();

const JobsTable = () => {
  const columns = useMemo(
    () => [
      columnHelper.accessor("expander", {
        id: "expander",
        size: 20,
        enableSorting: false,
        header: () => (
          <>
            <button onClick={table.getToggleAllRowsExpandedHandler()}>
              {table.getIsAllRowsExpanded() ? "-" : "+"}
            </button>
          </>
        ),
        cell: (info) => {
          return (
            <div className="relative">
              {info.row.getCanExpand() && (
                <button onClick={info.row.getToggleExpandedHandler()}>
                  <Image
                    src="/table_expander.svg"
                    fill={true}
                    alt="table expander"
                    className={` ${
                      info.row.getIsExpanded() ? "rotate-180 " : "rotate-0 "
                    }`}
                  />
                </button>
              )}
              {}
            </div>
          );
        },
      }),

      columnHelper.accessor("Serial", {
        header: "Serial",
        cell: (props) => <p>{props.getValue()}</p>,
        size: 80,
      }),
      
      // rest of the columns similar to above Serial
      
    ],
    [],
  );

  const [columnFilters, setColumnFilters] = useState([]);
  const [expanded, setExpanded] = useState(false);

  const {
    data: jobsData,
    error: jobsError,
    isLoading: jobsIsLoading,
  } = useSWR("http://localhost:9600/Job/jobs", fetcher);

  const table = useReactTable({
    data: jobsData,
    columns,
    state: {
      columnFilters,
      expanded,
    },
    getCoreRowModel: getCoreRowModel(),
    getExpandedRowModel: getExpandedRowModel(),
    getSortedRowModel: getSortedRowModel(),
    onExpandedChange: setExpanded,
    getSubRows: (row) => row.Tasks,
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    initialState: {
      pagination: {
        pageSize: 27,
      },
    },
    columnResizeMode: "onChange",
  });

  if (jobsError) return <div>failed to load</div>;
  if (jobsIsLoading) return <div>loading...</div>;

  return (
    <div className="relative grid h-screen w-full grid-cols-[0.5fr_1fr] grid-rows-[40px_700px_80px] gap-y-1">
      <JobsFilterBox
        className="col-[1_/_3] row-[1_/_2]"
        columnFilters={columnFilters}
        setColumnFilters={setColumnFilters}
      />
      <div className="col-[1_/_3] row-[2_/_3] overflow-x-scroll">
        <table
          className={
            "border-l border-t border-gray-300 bg-white text-left text-xs"
          }
          style={{ width: table.getTotalSize() }}
        >
          <thead>
            {table.getHeaderGroups().map((headerGroup) => (
              <tr className="relative flex" key={headerGroup.id}>
                {headerGroup.headers.map((header) => (
                  <th
                    className={
                      "relative flex items-center justify-center border-b border-r border-gray-300 bg-[#2170ba] p-2 text-center text-gray-100"
                    }
                    style={{ width: header.getSize() }}
                    key={header.id}
                  >
                    {header.column.columnDef.header}
                    {header.column.getCanSort() && (
                      <div className="relative ml-2 h-3 w-3 cursor-pointer">
                        <Image
                          fill={true}
                          src="../sort_icon.svg"
                          alt="sort icon"
                          onClick={header.column.getToggleSortingHandler()}
                        />
                      </div>
                    )}
                    <div
                      onMouseDown={header.getResizeHandler()}
                      onTouchStart={header.getResizeHandler()}
                      className={`${
                        header.column.getIsResizing()
                          ? "bg-orange-600 opacity-100"
                          : ""
                      } absolute right-0 top-0 h-full w-[5px] cursor-col-resize touch-none select-none  bg-blue-900 opacity-0 hover:opacity-100`}
                    ></div>
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody className="">
            {table.getRowModel().rows.map((row) => (
              <tr
                className={` relative flex h-6 ${
                  row.depth > 0
                    ? "bg-[#afd6f4] text-gray-700"
                    : "bg-[#93c8f0] text-gray-700"
                } ${
                  row.depth === 1 && row.id.includes(".0")
                    ? "border-t border-gray-400"
                    : ""
                } `}
                key={row.id}
              >
                {row.getVisibleCells().map((cell) => (
                  <td
                    className={`truncate border-b border-r border-gray-300 ${
                      cell.column.id === "State"
                        ? getStatusColor(row.original.Status)
                        : ""
                    } `}
                    style={{ width: cell.column.getSize() }}
                    key={cell.id}
                  >
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="col-span-2 col-start-1 row-[3_/_4] flex flex-[1_0] flex-row flex-nowrap">
        <div className="">
          <button
            className="mr-2 w-fit shrink-0"
            onClick={() => table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            {"<"}
          </button>
        </div>
        <div className="">
          <p className="w-fit text-sm text-gray-900">
            Page {table.getState().pagination.pageIndex + 1} of{" "}
            {table.getPageCount()}
          </p>
        </div>
        <div className="">
          <button
            className="ml-2 w-fit"
            onClick={() => table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            {">"}
          </button>
        </div>
      </div>
    </div>
  );
};

Solution

  • I replaced this line:

    {header.column.columnDef.header}
    

    With this:

    <span>{flexRender(header.column.columnDef.header, header.getContext())}</span>
    

    And now it works.