shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
62.84k stars 3.53k forks source link

Add example of data table using createColumnHelper from Tanstack Table #346

Open ahkhanjani opened 1 year ago

ahkhanjani commented 1 year ago

createColumnHelper function in my opinion makes the TypeScript experience much better. It would be great to have it in the docs. The current example works at run time but gives a type error

its-monotype commented 1 year ago

Do you know if this "Type instantiation is excessively deep and possibly infinite" related to columnHelper has already been fixed?

https://github.com/TanStack/table/issues/4224#issuecomment-1423946421 https://github.com/TanStack/table/issues/4387 https://github.com/TanStack/table/issues/4241

dBianchii commented 1 year ago

Yes, I am trying to make this work... I've done everything, but I can't get the DataTable component columns prop to accept my columns generated by createColumnHelper. Here's what I mean: image

A you can see in the image, I can send columns to the normal useReactTable hook, but not to the DataTable component columns prop....

I'm trying to make this work. Does anyone know the solution?

dBianchii commented 1 year ago

I kind of 'fixed it' by removing TValue generic in the component,a nd placing any for ColumnDef<TData, TValue>[]; instead of TValue

ahkhanjani commented 1 year ago

Do you know if this "Type instantiation is excessively deep and possibly infinite" related to columnHelper has already been fixed?

Hi. I didn't know that issue existed. I will look into it.

I kind of 'fixed it' by removing TValue generic in the component,a nd placing any for ColumnDef<TData, TValue>[]; instead of TValue

I'm just ts-ignore'ing where I pass the columns prop for now.

mortona42 commented 10 months ago

Here's what I got. I'm new to typescript but I bashed my way through it by trying to understand the IDE errors and looking up expected values. Probably lots to improve here.

One trick is defining the object returned from columnHelper.accessor() as ColumnDef<Task>.

Without this, it complains: Type 'ColumnDef<{ id: string; title: string; status: string; label: string; priority: string; }, string>' is not assignable to type 'ColumnDef<{ id: string; title: string; status: string; label: string; priority: string; }>'.

Alternatively, you can declare the column array definition as ColumnDef<Task, string> and remove as ColumnDef<Task> from each column.

I couldn't figure out how to do this with just the exported constant, so I added a createColumns() function that the constant calls to get values. Is there a better way to do this?

function createColumns(): ColumnDef<Task>[] {
  const columnHelper = createColumnHelper<Task>()

  return [
    columnHelper.display({
      id: "select",
      header: ({ table }) => (
        <Checkbox
          checked={table.getIsAllPageRowsSelected()}
          onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
          aria-label="Select all"
          className="translate-y-[2px]"
        />
      ),
      cell: ({ row }) => (
        <Checkbox
          checked={row.getIsSelected()}
          onCheckedChange={(value) => row.toggleSelected(!!value)}
          aria-label="Select row"
          className="translate-y-[2px]"
        />
      ),
      enableSorting: false,
      enableHiding: false,
    }),

    columnHelper.accessor('id', {
      header: ({ column }) => (
        <DataTableColumnHeader column={column} title="Task" />
      ),
      cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
      enableSorting: false,
      enableHiding: false,
    }) as ColumnDef<Task>,

    columnHelper.accessor('title', {
      header: ({ column }) => (
        <DataTableColumnHeader column={column} title="Title" />
      ),
      cell: ({ row }) => {
        const label = labels.find((label) => label.value === row.original.label)

        return (
          <div className="flex space-x-2">
            {label && <Badge variant="outline">{label.label}</Badge>}
            <span className="max-w-[500px] truncate font-medium">
              {row.getValue("title")}
            </span>
          </div>
        )
      },
    }) as ColumnDef<Task>,

    columnHelper.accessor('priority', {
      header: ({ column }) => (
        <DataTableColumnHeader column={column} title="Status" />
      ),
      cell: ({ row }) => {
        const status = statuses.find(
          (status) => status.value === row.getValue("status")
        )

        if (!status) {
          return null
        }

        return (
          <div className="flex w-[100px] items-center">
            {status.icon && (
              <status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
            )}
            <span>{status.label}</span>
          </div>
        )
      },
      filterFn: (row, id, value) => {
        return value.includes(row.getValue(id))
      },
    }) as ColumnDef<Task>,

    columnHelper.accessor('status', {
    header: ({ column }) => (
      <DataTableColumnHeader column={column} title="Priority" />
    ),
    cell: ({ row }) => {
      const priority = priorities.find(
        (priority) => priority.value === row.getValue("priority")
      )

      if (!priority) {
        return null
      }

      return (
        <div className="flex items-center">
          {priority.icon && (
            <priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
          )}
          <span>{priority.label}</span>
        </div>
      )
    },
    filterFn: (row, id, value) => {
      return value.includes(row.getValue(id))
    },
    }) as ColumnDef<Task>,

    columnHelper.display({
      id: 'actions',
      cell: ({ row }) => <DataTableRowActions row={row} />,
    })
  ]
}

export const columns: ColumnDef<Task>[] = createColumns()
braginteractive commented 5 months ago

I kind of 'fixed it' by removing TValue generic in the component,a nd placing any for ColumnDef<TData, TValue>[]; instead of TValue

This fixed the error.. Bit of a hack..

interface DataTableProps<TData> {
  columns: any;
  data: TData[];
}
dBianchii commented 5 months ago

I still don't get it, why don't the docs use column helper @shadcn ?

mamlzy commented 5 months ago

I still don't get it, why don't the docs use column helper @shadcn ?

agreed, using column helper for better type safety @shadcn, thanks for all your works..

austinm911 commented 5 months ago

This seems to work for me

const columnHelper = createColumnHelper<Contact>()
export const columns: ColumnDef<Contact>[] = [

    columnHelper.accessor('firstName', {
        header: 'First Name',
        cell: (info) => info.getValue(),
    }),

    columnHelper.accessor('lastName', {
        id: 'lastName',
        header: 'Last Name',
        cell: (info) => info.getValue(),
    })
] as Array<ColumnDef<Contact, unknown>>
interface DataTableProps<TData, TValue> {
    columns: ColumnDef<TData, TValue>[]
    data: TData[]
}
a89529294 commented 1 month ago

This seems to work for me

const columnHelper = createColumnHelper<Contact>()
export const columns: ColumnDef<Contact>[] = [

  columnHelper.accessor('firstName', {
      header: 'First Name',
      cell: (info) => info.getValue(),
  }),

  columnHelper.accessor('lastName', {
      id: 'lastName',
      header: 'Last Name',
      cell: (info) => info.getValue(),
  })
] as Array<ColumnDef<Contact, unknown>>
interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}

You actually just need the last part as Array<ColumnDef<Contact, unknown>>. Thanks for the solution.