Next.js Discord

Discord Forum

Shadcn Datatable/React Table pass additional data to columns

Answered
Eastern Phoebe posted this in #help-forum
Open in Discord
Eastern PhoebeOP
I'm currently working on a Next.js project where I'm utilizing React Table to create a DataTable component. The column definitions are located in a separate file called Columns.tsx. While I can successfully pass data to the DataTable component in my page.tsx file, I've encountered a browser error that's not reflected in the editor.

Here's a simplified version of the relevant code:
page.tsx
// ... (imports)

export default async function Page() {
  const { orgId } =  auth()

  const projects = await db.project.findMany({
    // ... (database queries)
  })

  const customerEmails = await db.customer.findMany({
    // ... (database queries)
  })

  return (
    <>
      {orgId ? (
        <main>
          <Container className="h-[80vh] flex justify-center items-center">
            {/* ... (other UI components) */}
            <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
              {/* I want to pass customerEmails to the columns file here */}
              <DataTable columns={columns({ customerEmails })} data={projects} />
            </div>
            {/* ... (other UI components) */}
          </Container>
        </main>
      ) : (
        // ... (error handling UI components)
      )}
    </>
  );
}


Columns.tsx
// ... (imports)

type ProjectColumns = Omit<Project, "customerId"> & {
  customer: {
    email: string;
  };
}

export const columns = ({ customerEmails }: { customerEmails: { email: string }[] }): ColumnDef<ProjectColumns>[] => [
  // ... (column definitions)
];


As mentioned above I don't habe any errors in my Editor (Visual Studio Code) but in the browser (see attached screenshot). Im using Nextjs 14.
Answered by Ray
can you pass customerEmails to DataTable component?
View full answer

41 Replies

Forest bachac
Just do columns={columns} no? You export columns as a const in columns.tsx so you don't need to call it as a function broski
Eastern PhoebeOP
Hi @Forest bachac , the problem is that I want to pass additional data from the database to the columns.tsx so I can work with it in the "Actions" column.
@Eastern Phoebe Hi <@578670669970276393> , the problem is that I want to pass additional data from the database to the columns.tsx so I can work with it in the "Actions" column.
Forest bachac
I think you have got the answer in your response - Do that in columns.tsx then? Is that possible
@Forest bachac I think you have got the answer in your response - Do that in columns.tsx then? Is that possible
Eastern PhoebeOP
Columns.tsx is a Client Component. As far as i know i can only get the prisma database data in a Servercomponent and pass it down to the client component.
@Ray I think it should be an array?
Eastern PhoebeOP
so Column.tsx is marked with "use client" at the top and i used the datatable from shadcnui. my problem is that i have another client component (EditProjectDialog) where i need to pass extra data from the database. it looks like the following:
"use client"

export const columns = ({ customerEmails }: { customerEmails: { email: string }[] }): ColumnDef<ProjectColumns>[] => [
  {
    accessorKey: "name",
    header: "Name",
  },
  {
    accessorKey: "customer",
    accessorFn: (row) => row.customer.email,
    header: ({ column }) => {
      return (
        <Button
          variant="ghost"
          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
        >
          Customer
          <ArrowUpDown className="ml-2 h-4 w-4" />
        </Button>
      )
    },
  },
  {
    accessorKey: "description",
    header: "Description",
  },
  {
    accessorKey: "status",
    header: "Status",
  },
  {
    id: "actions",
    cell: async ({ row }) => {
      const project = row.original

      return (
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="ghost" className="h-8 w-8 p-0">
              <span className="sr-only">Open menu</span>
              <MoreHorizontal className="h-4 w-4" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            <DropdownMenuLabel>Actions</DropdownMenuLabel>
            <EditProjectDialog project={project} customerEmails={customerEmails} /> // HERE IM PASSING THE CUSTOMEREMAILS I WANT TO PASS DOWN FROM THE PAGE.TSX
            <DropdownMenuSeparator />
            <DeleteCustomerDialog email={project.name} />
          </DropdownMenuContent>
        </DropdownMenu>
      )
    },
  },
]
@Eastern Phoebe so Column.tsx is marked with "use client" at the top and i used the datatable from shadcnui. my problem is that i have another client component (EditProjectDialog) where i need to pass extra data from the database. it looks like the following: tsx "use client" export const columns = ({ customerEmails }: { customerEmails: { email: string }[] }): ColumnDef<ProjectColumns>[] => [ { accessorKey: "name", header: "Name", }, { accessorKey: "customer", accessorFn: (row) => row.customer.email, header: ({ column }) => { return ( <Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} > Customer <ArrowUpDown className="ml-2 h-4 w-4" /> </Button> ) }, }, { accessorKey: "description", header: "Description", }, { accessorKey: "status", header: "Status", }, { id: "actions", cell: async ({ row }) => { const project = row.original return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" className="h-8 w-8 p-0"> <span className="sr-only">Open menu</span> <MoreHorizontal className="h-4 w-4" /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuLabel>Actions</DropdownMenuLabel> <EditProjectDialog project={project} customerEmails={customerEmails} /> // HERE IM PASSING THE CUSTOMEREMAILS I WANT TO PASS DOWN FROM THE PAGE.TSX <DropdownMenuSeparator /> <DeleteCustomerDialog email={project.name} /> </DropdownMenuContent> </DropdownMenu> ) }, }, ]
you should remove 'use client' in column.tsx or just put it on the page
and make the dropdown menu a client component
@Ray and make the dropdown menu a client component
Eastern PhoebeOP
i think it need to be a client component this is the minimal example i found on shadcnui:
"use client"
 
import { ColumnDef } from "@tanstack/react-table"
 
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type Payment = {
  id: string
  amount: number
  status: "pending" | "processing" | "success" | "failed"
  email: string
}
 
export const columns: ColumnDef<Payment>[] = [
  {
    accessorKey: "status",
    header: "Status",
  },
  {
    accessorKey: "email",
    header: "Email",
  },
  {
    accessorKey: "amount",
    header: "Amount",
  },
]
can you pass customerEmails to DataTable component?
Answer
then in the cell map do this
{row.getVisibleCells().map((cell) => (
  <TableCell key={cell.id}>
    {flexRender(cell.column.columnDef.cell, {
      ...cell.getContext(),
      customerEmails,
    })}
  </TableCell>
))}
then you should be able to access it from the cell
{
    id: "action",
    cell(props) {
      console.log(props.customerEmails)
    },
  },
and change columns() back to columns
this is for the type
declare module "@tanstack/react-table" {
  interface CellContext<TData extends unknown, TValue> {
    customerEmails: string[];
  }
}
@Ray can you pass `customerEmails` to `DataTable` component?
Eastern PhoebeOP
Hi @Ray , sorry for the late response. I've implemented your code and it worked. Thanks a lot. But I had some problems with the type. My types file now looks like this:
import { CellContext, HeaderContext } from "@tanstack/react-table";

type CustomCellContext<TData, TValue> = {
  customerEmails: { email: string }[];
} & CellContext<TData, TValue>;

type CustomHeaderContext<TData, TValue> = {
  customerEmails: { email: string }[];
} & HeaderContext<TData, TValue>;


Columns.tsx:
cell: ((props: CellContext<ProjectColumns, unknown> & { customerEmails: { email: string }[] }) => {
// some code
}


Datatable.tsx:
...
<TableHead key={header.id}>
  {header.isPlaceholder
    ? null
    : flexRender(
        header.column.columnDef.header,
        header.getContext() as CustomHeaderContext<TData, TValue> // aded type
      )}
</TableHead>
...
<TableRow
  key={row.id}
  data-state={row.getIsSelected() && "selected"}
>
  {row.getVisibleCells().map((cell) => (
    <TableCell key={cell.id}>
      {flexRender(cell.column.columnDef.cell, {
        ...cell.getContext(),
        customerEmails,
      } as CustomCellContext<TData, TValue>)} // added type
    </TableCell>
  ))}
</TableRow>
...


My code works perfectly fine now but I think theres a better solution for the types. Sadly I'm not a Typescript Expert. 🤔
@Ray have you tried this?
Eastern PhoebeOP
Yeah tried that but I had multiple type erros. I also tried it with {email: string}[] which is the correct type for customerEmails
is customerEmails an array of string?
or array of object?
can you show the schema of it?
@Ray is customerEmails an array of string?
Eastern PhoebeOP
its customerEmails: {email: string}[] I'm 100% sure
can you show the error you got?
@Ray can you show the error you got?
Eastern PhoebeOP
:this_is_fine:
lol
why you have so many type error
can you show the code in Data-Table.tsx and Columns.tsx
Eastern PhoebeOP
ignore the commented code (its my type-chaos that worked)
declare module "@tanstack/react-table" {
  interface CellContext<TData extends unknown, TValue> {
    customerEmails: { email: string }[];
  }
}

this should work
@Ray ts declare module "@tanstack/react-table" { interface CellContext<TData extends unknown, TValue> { customerEmails: { email: string }[]; } } this should work
Eastern PhoebeOP
Hmm i got the same code in my types/tanstacktable.d.ts but i still got the typescript errors. Am I missing something like imports or some npm package?
@Ray try putting this in the `Columns.tsx`
Eastern PhoebeOP
the errors still remain
what do you have in types/tanstacktable.d.ts
I got this code without error
//data-table.tsx
"use client";

import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
}

export function DataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    state: {},
  });

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => {
                return (
                  <TableHead key={header.id}>
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                  </TableHead>
                );
              })}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows?.length ? (
            table.getRowModel().rows.map((row) => (
              <TableRow
                key={row.id}
                data-state={row.getIsSelected() && "selected"}
              >
                {row.getVisibleCells().map((cell) => (
                  <TableCell key={cell.id}>
                    {flexRender(cell.column.columnDef.cell, {
                      ...cell.getContext(),
                      hello: "123",
                    })}
                  </TableCell>
                ))}
              </TableRow>
            ))
          ) : (
            <TableRow>
              <TableCell colSpan={columns.length} className="h-24 text-center">
                No results.
              </TableCell>
            </TableRow>
          )}
        </TableBody>
      </Table>
    </div>
  );
}
// page.tsx
import { Payment, columns } from "./columns";
import { DataTable } from "./data-table";

async function getData(): Promise<Payment[]> {
  // Fetch data from your API here.
  return [
    {
      id: "728ed52f",
      amount: 100,
      status: "pending",
      email: "m@example.com",
    },
    {
      id: "489e1d42",
      amount: 125,
      status: "processing",
      email: "example@gmail.com",
    },
    // ...
  ];
}

export default async function DemoPage() {
  const data = await getData();

  return (
    <div className="container mx-auto py-10">
      <DataTable columns={columns} data={data} />
    </div>
  );
}
// columns.tsx
"use client";

import { ColumnDef, CellContext } from "@tanstack/react-table";
import { Button } from "react-aria-components";

export type Payment = {
  id: string;
  amount: number;
  status: "pending" | "processing" | "success" | "failed";
  email: string;
};

declare module "@tanstack/react-table" {
  interface CellContext<TData extends unknown, TValue> {
    hello: string;
  }
}

export const columns: ColumnDef<Payment>[] = [
  {
    accessorKey: "status",
    header: "Status",
  },
  {
    accessorKey: "email",
    header: "Email",
  },
  {
    accessorKey: "amount",
    header: "Amount",
  },
  {
    id: "action",
    cell(props: CellContext<Payment, unknown>) {
      return <Button>{props.hello}</Button>;
    },
  },
];
@Ray what do you have in `types/tanstacktable.d.ts`
Eastern PhoebeOP
I just uncommented this module code in my tanstacktable.d.ts file and somehow the errors disappeared I dont get why but i thank you so much.
declare module "@tanstack/react-table" {
   interface CellContext<TData extends unknown, TValue> {
     customerEmails: {email: string}[];
   }
 }
np glad you fixed it