TanStack / table

🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
https://tanstack.com/table
MIT License
25.09k stars 3.07k forks source link

ColumnDef types gives typescript error #4382

Open Jontii opened 2 years ago

Jontii commented 2 years ago

Describe the bug

Looking at the examples and docs I expect this to correctly type my columns for me. Instead I get a large error with this code:

  type Example = {
    name: string
    age: number
  }

  const columnHelper = createColumnHelper<Example>()

  const columns = useMemo<ColumnDef<Example>[]>(
    () => [
      columnHelper.accessor("name", {
        cell: (info) => info.getValue(),
      }),
      columnHelper.accessor("age", {
        cell: (info) => info.getValue(),
      }),
    ],
    [columnHelper],
  )
bild

Am I doing something wrong here?

Regards Jonathan

Your minimal, reproducible example

https://codesandbox.io/s/typescript-playground-export-forked-iqm265?file=/index.tsx

Steps to reproduce

  1. Create a type with two different types, string and name for example.
  2. Use the createColumnHelper with the type
  3. Define columns with columnHelper and type them with ColumnDef

Expected behavior

I expected the Columdef to correctly type my columns.

How often does this bug happen?

Every time

Screenshots or Videos

bild

Platform

Mac OS

react-table version

v8.5.13

TypeScript version

v4.8.2

Additional context

No response

Terms & Code of Conduct

byroncoetsee commented 2 years ago

Only way I've found to get around this is to add a as string or some other primitive type after info.getValue()

j-fdion commented 2 years ago

I'm having the same problem, any updates on this?

phongplus commented 2 years ago

I m having same, so i fix with

const columns = useMemo<ColumnDef<Example, any>[]>

i don't know if this is the best way, but the typescript error should go away.

Jontii commented 2 years ago

We skipped using columnDef, it still works as good and gives type help.

  const columns = [
      columnHelper.accessor("name", {
        cell: (info) => info.getValue(),
      }),
      columnHelper.accessor("age", {
        cell: (info) => info.getValue(),
      }),
    ]
tannerlinsley commented 2 years ago

This is the way.

j-fdion commented 2 years ago

Thank you, it does work this way!

csandman commented 2 years ago

@tannerlinsley if you're saying @Jontii 's solution is the right way, you might want to change the Column Defs page in the docs to not use a column helper and a typed array. That is what you're saying right, either use one or the other?

Jontii commented 2 years ago

If I understand it, if you use grouped columns like this example https://tanstack.com/table/v8/docs/examples/react/column-sizing then it is correct. If you are not using grouped columns, you shouldn't use it.

tannerlinsley commented 2 years ago

You should only be using the column helper and not pre-typing anything.

csandman commented 2 years ago

@tannerlinsley This is the first example in the Column Defs guide page:

// Define your row shape
type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const columnHelper = createColumnHelper<Person>()

// Make some columns!
const defaultColumns: ColumnDef<Person>[] = [  // <- Pre typed Array
  // Display Column
  columnHelper.display({ // <- While using column helper
    id: 'actions',
    cell: props => <RowActions row={props.row} />,
  }),
  // Grouping Column
  columnHelper.group({
    header: 'Name',
    footer: props => props.column.id,
    columns: [
      // Accessor Column
      columnHelper.accessor('firstName', {
        cell: info => info.getValue(),
        footer: props => props.column.id,
      }),
      // Accessor Column
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => <span>Last Name</span>,
        footer: props => props.column.id,
      }),
    ],
  }),
  // ...

It has the column array pre-typed and throws errors if any of these accessor columns are top level.

tannerlinsley commented 2 years ago

Yeah… we should fix that. On Oct 5, 2022 at 11:40 AM -0700, Chris Sandvik @.***>, wrote:

@tannerlinsley This is the first example in the Column Defs guide page: // Define your row shape type Person = { firstName: string lastName: string age: number visits: number status: string progress: number }

const columnHelper = createColumnHelper()

// Make some columns! const defaultColumns: ColumnDef[] = [ // <- Pre typed Array // Display Column columnHelper.display({ // <- While using column helper id: 'actions', cell: props => , }), // Grouping Column columnHelper.group({ header: 'Name', footer: props => props.column.id, columns: [ // Accessor Column columnHelper.accessor('firstName', { cell: info => info.getValue(), footer: props => props.column.id, }), // Accessor Column columnHelper.accessor(row => row.lastName, { id: 'lastName', cell: info => info.getValue(), header: () => Last Name, footer: props => props.column.id, }), ], }), // ... It has the column array pre-typed and throws errors if any of these accessor columns are top level. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

swernerx commented 2 years ago

Pre-typing would be helpful for public interfaces e.g. if we create a wrapper components with columns and data props. I am using this here right now:

interface DataTableProps {
  // FIXME: Can we figure out something more type restrictive which actually works?
  data: unknown[]
  columns: ColumnDef<any, any>[];
}

That makes TypeScript "happy"... but I would prefer something more strict.

I guess I was looking for a typed CustomTableComponent example which uses TanStack/Table underneath.

invalidBan commented 1 year ago

Pre-typing would be helpful for public interfaces e.g. if we create a wrapper components with columns and data props. I am using this here right now:

This! We're making a custom MyTableComponent and expose columns as prop that is pre-typed.

jonahallibone commented 1 year ago

Pre-typing would be helpful for public interfaces e.g. if we create a wrapper components with columns and data props. I am using this here right now:

interface DataTableProps {
  // FIXME: Can we figure out something more type restrictive which actually works?
  data: unknown[]
  columns: ColumnDef<any, any>[];
}

That makes TypeScript "happy"... but I would prefer something more strict.

I guess I was looking for a typed CustomTableComponent example which uses TanStack/Table underneath.

This makes typescript happy IF you allow use of any which my codebase explicitly does not and which TS does not recommend

skuridin commented 1 year ago

It becomes an issue when passing columns to a component as a prop.

LoicKairon commented 1 year ago

I am having the same issue here. Is there a way to avoid typing any?

type Props<T> = {
  data: T[];
  columns: ColumnDef<T, any>[];
};
IanVS commented 1 year ago

The EditableData example also uses React.useMemo<ColumnDef<Person>[]>(, I guess that's not correct either?

AlexMachin1997 commented 1 year ago

@LoicKairon Glad I'm not the only one having this issue, I've done the same as you for now but would but nice to get that work properly.

Patriksafar commented 1 year ago

Hey @tannerlinsley 👋 I there any update on "any" type issue? It seems this issue is dead without being resolved for long time than I would expect. Should I open separated issue instead? We really love this library but having proper type instead of any would really help us.

I am having the same issue here. Is there a way to avoid typing any?

type Props<T> = {
  data: T[];
  columns: ColumnDef<T, any>[];
};
Armadillidiid commented 1 year ago

I just encountered the same issue, which forced me to turn off no-explicit-any.

DoubleJ-G commented 1 year ago

Same use case as above, useTable is wrapped in a reuseable component that takes in columns as a prop. Trying to use columHelper to create the definitions in the parent and it causes a typescript error.

blumaa commented 1 year ago

Any update on this issue? Running into the same issue. My solution is to use any for the type and individually define the types for each cell:

type BillingDetails = {
  id: number;
  service: string;
  talentName: string;
  amount: number;
  createdAt: string;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any 
const columns: ColumnDef<BillingDetails, any>[] = [
  columnHelper.accessor('service', {
    id: 'service',
    cell: (props) => props.getValue() as string,
    header: 'Service',
    enableSorting: false,
  }),
  columnHelper.accessor('talentName', {
    id: 'talentName',
    cell: (props) => props.getValue() as string,
    header: 'Talent name',
    enableSorting: false,
  }),
  columnHelper.accessor('amount', {
    id: 'amount',
    cell: (props) => props.getValue() as number,
    header: 'Amount',
    enableSorting: false,
  }),
  columnHelper.accessor('createdAt', {
    id: 'createdAt',
    cell: (props) => props.getValue() as string,
    header: 'Created at',
    enableSorting: false,
  }),
]
cameronthrntn commented 11 months ago

Getting the same here, trying to convince our team to migrate to @tanstack/react-table and this is certainly getting in the way of that. I've defined columns as such: (nothing revolutionary)

  const columnHelper = createColumnHelper<Coffee>();

  const columns = [
    columnHelper.accessor(data => data.id, {
      id: 'id',
      cell: info => info.getValue()
    })
  ];

and have typed my generic table component as:

interface TableProps<T extends object> {
  data: T[];
  columns: ColumnDef<T>[];
}

export default function index<T extends object>({ columns, data }: TableProps<T>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel()
  });

(...)

But am then getting the following Type Error when passing my columns as a prop:

        Property 'accessorFn' is missing in type 'ColumnDefBase<Coffee, number> & StringHeaderIdentifier' but required in type 'AccessorFnColumnDefBase<Coffee, unknown>'.ts(2322)
types.d.ts(83, 5): 'accessorFn' is declared here.
index.tsx(6, 3): The expected type comes from property 'columns' which is declared here on type 'IntrinsicAttributes & TableProps<Coffee>'

Any advice is appreciated

react-table version: 8.10.7 @types/react-table version: 7.7.18

arkmech commented 10 months ago

Yeah getting same issue.

suresh-laradev commented 10 months ago

is there any solution for this. this still exists.

ngurahyudi commented 10 months ago

Waiting for the solution to this issue too.

Ac-Srikanth commented 10 months ago

yeah please i really require a solution for this.

PeterEckIII commented 10 months ago

Any progress here? Going to have to drop react-table if this isn't fixed, unfortunately.

AlexMachin1997 commented 10 months ago

Any progress here? Going to have to drop react-table if this isn't fixed, unfortunately.

That's very excessive, just put any for the second argument and it'll still work really well.

fs-swhittle commented 9 months ago

Can anyone provide a working vue-table example project that passes column info into a table component? I'm using typescript and getting the following error no matter how I move the types around: Uncaught (in promise) TypeError: columnDefs2 is undefined

Edit: An example that works even with the any type would be awesome, I can't even get that to work.

netbull commented 8 months ago

for the time being I just removed the ColumnDef<Example>[] from const columns: ColumnDef<Example>[] = [...] and all worked just fine.

My generic table component have prop columns?: ColumnDef<T, any>[]; and it's also fine.

AndKenneth commented 8 months ago

For me it seems to be anywhere that I'm returning a non-string from the accessor.

image

vs.

image

Having to cast everything to a string and back isn't the best.

0xTux commented 8 months ago

still waiting for the official fix lol

anth0ni3 commented 8 months ago

Downgrade to 8.10.3 and it works.

kdavid14 commented 7 months ago

This still happens only when you have multiple types on your object property

export type User = {
  id: number;
  name: string;
  email: string;
  role: "admin" | "member";
  status: "active" | "pending";
};

// changing everything with type of string fixed the issue but whats the point of using typescript here
export type User = {
  id: string;
  name: string;
  email: string;
  role: string;
  status: string;
};
ByteWither commented 7 months ago

@swernerx can you get some comments about this issue? Are you planning on fixing this? Please pay special attention to @cameronthrntn comment

patelnets commented 7 months ago

Still facing this issue

nurbek-mirai commented 6 months ago

Bump.

snsnusus commented 6 months ago

👀

RestartDK commented 6 months ago

Any update on this issue? What is the best workaround for this?

4ndrs commented 6 months ago

@RestartDK

I have been using the any approach when defining my table props:

// table component
type Props<T> = {
  data: T[];
  columns: ColumnDef<T, any>[];
};
// when using the table component

const columnHelper = createColumnHelper<Vehicle>();

const columns = [
  columnHelper.accessor("brand", {
    header: "Marca",
  }),
  columnHelper.accessor("model", {
    header: "Modello",
  }),
]

const Testing = () => {
  const vehicles = useVehicles();

  return (
      <Table data={vehicles} columns={columns} />
  );
};

I haven't had any issues with inference or missing types so far.

@tanstack/react-table: "^8.15.3"

AlexMachin1997 commented 6 months ago

@4ndrs That's what I did previously, it's not ideal but it work's perfectly fine tbh.

This is my demo from what I created a tanstack table at my last role, see this

I wish people would stop spamming this thread 😆 There a good few answers here now, give them a go and see if any of them help you out.

@tannerlinsley and all the other maintainers/typescript wizards have done an incredible job with this library especially after it got a massive upgrade in ❤️ It's really difficult to get this sort stuff right so I'm sure in this particular case an "any" or "unknown" cast will do the trick everything else works great last time I checked.

I'm going to unsubscribe from this thread as I keep getting emails about it 🤣

Tchekda commented 5 months ago

Hello, I am still facing the same issue on 8.15.0 and 8.15.3. I am following the shadcn example (Preview / Code) Can't make it work with

const columns: ColumnDef<Task, any>[] = [

nor

const columns: ColumnDef<Task>[] = [

I am still getting the following error:

Type 'ColumnDef<Task, any>[]' is not assignable to type 'ColumnDef<unknown, any>[]'.
  Type 'ColumnDef<Task, any>' is not assignable to type 'ColumnDef<unknown, any>'.
    Type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' is not assignable to type 'ColumnDef<unknown, any>'.
      Type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' is not assignable to type 'AccessorFnColumnDefBase<unknown, any> & IdIdentifier<unknown, any>'.
        Property 'accessorFn' is missing in type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' but required in type 'AccessorFnColumnDefBase<unknown, any>'.

The only workaround I use for now is:

columns: columns as ColumnDef<unknown>[],
emimaricic commented 5 months ago

Somebody help please. I added custom variable to columnDef and typescript is not recognizing it I don't know how to fix it. This is the error: Property 'category' does not exist on type 'ColumnDef<TData, unknown>'. Property 'category' does not exist on type 'ColumnDefBase<TData, unknown> & StringHeaderIdentifier'

import { Button } from './button';
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuTrigger,
} from './dropdown-menu';
import { PaginationSection } from './pagination-section';
import {
  flexRender,
  ColumnFiltersState,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  useReactTable,
  ColumnDef,
} from '@tanstack/react-table';
import { ChevronDown } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import queryString from 'query-string';
import { useEffect, useRef, useState } from 'react';
import { useDebounce } from 'use-debounce';

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

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
  total: number;
  onRowClick?: [keyof TData, (value: TData[keyof TData]) => void];
}

export function DataTable<TData, TValue>({
  columns,
  data,
  total,
  onRowClick,
}: DataTableProps<TData, TValue>) {
  const router = useRouter();
  const searchParams = useSearchParams();
  const paramsObject = Object.fromEntries(searchParams);
  const filtersString = queryString.stringify(
    Object.fromEntries(
      Object.entries(paramsObject).filter(([key, value]) => key !== 'search')
    )
  );

  const initialRender = useRef(true);
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);

  const [isMounted, setIsMounted] = useState(false);
  const [text, setText] = useState(paramsObject.search);
  const [searchQuery] = useDebounce(text, 750);

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onColumnFiltersChange: setColumnFilters,
    getFilteredRowModel: getFilteredRowModel(),
    state: {
      columnFilters,
    },
    enableHiding: true,
    initialState: {
      pagination: { pageSize: parseInt(paramsObject.limit || '10') },
    },
  });

  useEffect(() => {
    if (!initialRender.current) {
      initialRender.current = true;
      return;
    }

    if (searchQuery) {
      router.push(`?${filtersString}&search=${searchQuery}`);
    } else if (searchQuery === '') router.push(`?${filtersString}`);
  }, [searchQuery]);

  useEffect(() => {
    setIsMounted(true);
  }, [isMounted]);

  if (!isMounted) return null;

  return (
    <div className="flex flex-col w-full overflow-x-auto">
      <div className="w-full flex items-center justify-between py-4">
...
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant="outline" className="ml-auto">
              Columns <ChevronDown className="ml-2 h-4 w-4" />
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end">
            {table
              .getAllColumns()
              .filter((column) => column.getCanHide())
              .map((column) => (
                <DropdownMenuCheckboxItem
                  key={column.id}
                  className="capitalize"
                  checked={column.getIsVisible()}
                  onCheckedChange={(value) => column.toggleVisibility(!!value)}
                >
                  {column.id}
                </DropdownMenuCheckboxItem>
              ))}
          </DropdownMenuContent>
        </DropdownMenu>
      </div>
      <div>
        <Button
          variant={'outline'}
          onClick={() => {
            table
              .getAllColumns()
              .filter((column) => column.getCanHide())
              .forEach((column) => {
                if (column.columnDef.category === 'ATTACKING') {
                  column.toggleVisibility(!column.getIsVisible());
                }
              });
          }}
        >
          Attacking
        </Button>
...
        </div>
    </div>
  );
}
mbaquerizo commented 5 months ago

I'm also trying to pass columns into a custom Table component that requires a tableLink field in the data.

interface DataTableProps<LinkableRows extends boolean | undefined, TData, TValue> {
  columns: LinkableRows extends true
    ? ColumnDef<TData & TableLink, TValue>[]
    : ColumnDef<TData, TValue>[];
  data: LinkableRows extends true ? (TData & TableLink)[] : TData[];
  linkRows?: LinkableRows;
}

type DataTableWithLinksProps<TData, TValue> = DataTableProps<true, TData, TValue>;

type DataTableWithoutLinksProps<TData, TValue> = DataTableProps<false, TData, TValue>;

const export function CustomTable<TData, TValue>({
  columns,
  data,
  linkRows
}: DataTableWithLinksProps<TData, TValue> | DataTableWithoutLinksProps<TData, TValue>) {
  const table = useReactTable({
    columns, // accessorFn error here 
    data,
    ...
  });

  // ...return table element that navigates to `row.original.tableLink` on
  // row click if linkRows is true
}

This would work if columns was defined as just ColumnDef<TData, TValue>. The TS Error occurs when adding the intersection with { tableLink: string } to the ColumnDef.

Strangely, if I replace the union props type definition for the component params, with either DataTableWithLinkProps only or DataTableWithoutLinksProps only, the error also goes away...

Any solution/ideas?

happylolonly commented 5 months ago

Any updates how to fix correct?

rajatjaswalmpprogramming commented 4 months ago

Hello, I am still facing the same issue on 8.15.0 and 8.15.3. I am following the shadcn example (Preview / Code) Can't make it work with

const columns: ColumnDef<Task, any>[] = [

nor

const columns: ColumnDef<Task>[] = [

I am still getting the following error:

Type 'ColumnDef<Task, any>[]' is not assignable to type 'ColumnDef<unknown, any>[]'.
  Type 'ColumnDef<Task, any>' is not assignable to type 'ColumnDef<unknown, any>'.
    Type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' is not assignable to type 'ColumnDef<unknown, any>'.
      Type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' is not assignable to type 'AccessorFnColumnDefBase<unknown, any> & IdIdentifier<unknown, any>'.
        Property 'accessorFn' is missing in type 'ColumnDefBase<Task, any> & StringHeaderIdentifier' but required in type 'AccessorFnColumnDefBase<unknown, any>'.

The only workaround I use for now is:

columns: columns as ColumnDef<unknown>[],

I am getting the below error while using like this. Type 'ColumnDef[]' is not assignable to type 'never'.

malininss commented 3 months ago

Any updates? Two years have already passed

dir commented 3 months ago

I thought I was going crazy, followed the docs to a T, defined some straightforward columns, but can't make a generic table component and pass the columns to them with type safety for the life of me. Glad to see I'm not alone I guess.

DreamEcho100 commented 3 months ago

I had something similar to this when I tried to pass the columns and define them between components

Type 'PostColumn[]' is not assignable to type 'ColumnDef<Record<string, unknown>, unknown>[]'.
  Type 'PostColumn' is not assignable to type 'ColumnDef<Record<string, unknown>, unknown>'.
    Type 'ColumnDefBase<Post, unknown> & StringHeaderIdentifier' is not assignable to type 'ColumnDef<Record<string, unknown>, unknown>'.
      Type 'ColumnDefBase<Post, unknown> & StringHeaderIdentifier' is not assignable to type 'AccessorFnColumnDefBase<Record<string, unknown>, unknown> & IdIdentifier<Record<string, unknown>, unknown>'.
        Property 'accessorFn' is missing in type 'ColumnDefBase<Post, unknown> & StringHeaderIdentifier' but required in type 'AccessorFnColumnDefBase<Record<string, unknown>, unknown>'.ts(2322)
types.d.ts(98, 5): 'accessorFn' is declared here.
index.jsx(26, 2): The expected type comes from property 'columns' which is declared here on type 'IntrinsicAttributes & ApiDataTableStoreProps<"posts.getMany", Record<string, unknown>, unknown>'

For some reason it worked with me when I changed the type that will be used on the ColumnDef from being defined by interface to be defined by type, for example:

From

interface Post {
 id: string;
 title: string;
 content: string;
}

To

type Post = {
 id: string;
 title: string;
 content: string;
}

And I have no idea of why and how