table icon indicating copy to clipboard operation
table copied to clipboard

`getValue` fails to infer correct type when `columnHelper.accessor` defined within `columnHelper.group`

Open crcorbett opened this issue 1 year ago • 2 comments

TanStack Table version

8.20.6

Framework/Library version

React 19.0.0

Describe the bug and the steps to reproduce it

When a column is created using the columnHelper.accessor() utility within a column group columnHelper.group() the type of getValue is not correctly inferred.

CodeSandbox: https://codesandbox.io/p/devbox/funny-bush-lfv773

Example:


type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50,
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80,
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10,
  },
]

const columnHelper = createColumnHelper<Person>()

const columns = [
  columnHelper.accessor('firstName', {
    cell: info => info.getValue(),
    footer: info => info.column.id,
  }),
  columnHelper.group({
    id:'group',
    columns:[
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        // Unsafe return of a value of type `any`.
        // eslint[@typescript-eslint/no-unsafe-return](https://typescript-eslint.io/rules/no-unsafe-return)
        // CellContext<Person, any>.getValue: <any>() => any
        cell: info => <i>{info.getValue()}</i>, 
        header: () => <span>Last Name</span>,
        footer: info => info.column.id,
      }),
    ]
  })
]

Potentially related issue: https://github.com/TanStack/table/issues/5065

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://codesandbox.io/p/devbox/funny-bush-lfv773

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

No, because I do not know how

Terms & Code of Conduct

  • [x] I agree to follow this project's Code of Conduct
  • [x] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

crcorbett avatar Jan 08 '25 06:01 crcorbett

I think the issue is TanStack Table's ColumnHelper type and all of the unknown types within it, which don't allow TValue to be inferred correctly. As a quick test, try updating it to the following in "/node_modules/@tanstack/table-core/build/lib/columnHelper.d.ts":

export type ColumnHelper<TData extends RowData> = {
  accessor: <
    TAccessor extends AccessorFn<TData> | DeepKeys<TData>,
    TValue extends TAccessor extends AccessorFn<TData, infer TReturn>
      ? TReturn
      : TAccessor extends DeepKeys<TData>
        ? DeepValue<TData, TAccessor>
        : never,
  >(
    accessor: TAccessor,
    column: TAccessor extends AccessorFn<TData> ? DisplayColumnDef<TData, TValue> : IdentifiedColumnDef<TData, TValue>,
  ) => TAccessor extends AccessorFn<TData> ? AccessorFnColumnDef<TData, unknown> : AccessorKeyColumnDef<TData, unknown>;

  display: <TValue = unknown>(column: DisplayColumnDef<TData, TValue>) => DisplayColumnDef<TData, TValue>;

  group: <TValue = unknown>(column: GroupColumnDef<TData, TValue>) => GroupColumnDef<TData, TValue>;
};

Here's a custom hook I've been using with this update - I tested it with your code and it seems to fix the issue:

import {
  createColumnHelper,
  getCoreRowModel,
  getFilteredRowModel,
  getSortedRowModel,
  useReactTable,
  type AccessorFn,
  type AccessorFnColumnDef,
  type AccessorKeyColumnDef,
  type DeepKeys,
  type DeepValue,
  type DisplayColumnDef,
  type GroupColumnDef,
  type IdentifiedColumnDef,
  type RowData,
  type SortingState,
  type TableOptions,
} from '@tanstack/react-table';
import { useRef, useState } from 'react';

/** TanStack Table's `ColumnHelper` doesn't infer types correctly. */
export type FixedColumnHelper<TData extends RowData> = {
  accessor: <
    TAccessor extends AccessorFn<TData> | DeepKeys<TData>,
    TValue extends TAccessor extends AccessorFn<TData, infer TReturn>
      ? TReturn
      : TAccessor extends DeepKeys<TData>
        ? DeepValue<TData, TAccessor>
        : never,
  >(
    accessor: TAccessor,
    column: TAccessor extends AccessorFn<TData> ? DisplayColumnDef<TData, TValue> : IdentifiedColumnDef<TData, TValue>,
  ) => TAccessor extends AccessorFn<TData> ? AccessorFnColumnDef<TData, unknown> : AccessorKeyColumnDef<TData, unknown>;

  display: <TValue = unknown>(column: DisplayColumnDef<TData, TValue>) => DisplayColumnDef<TData, TValue>;

  group: <TValue = unknown>(column: GroupColumnDef<TData, TValue>) => GroupColumnDef<TData, TValue>;
};

type CreateColumns<TData extends RowData> = (args: {
  columnHelper: FixedColumnHelper<TData>;
  sorting: SortingState;
  tableWrapperRef: React.RefObject<HTMLDivElement | null>;
}) => Array<
  | ReturnType<FixedColumnHelper<TData>['accessor']>
  | ReturnType<FixedColumnHelper<TData>['display']>
  | ReturnType<FixedColumnHelper<TData>['group']>
>;

interface UseTableProps<TData>
  extends Omit<
    TableOptions<TData>,
    'columns' | 'getCoreRowModel' | 'getFilteredRowModel' | 'getSortedRowModel' | 'onSortingChange' | 'state'
  > {
  createColumns: CreateColumns<TData>;
  defaultSorting?: SortingState;
  state?: Omit<TableOptions<TData>['state'], 'sorting'>;
}

export function useReactTableWithHelpers<TData>({
  createColumns,
  data,
  defaultSorting = [],
  state,
  ...tableOptions
}: UseTableProps<TData>) {
  const [sorting, setSorting] = useState<SortingState>(defaultSorting);

  const tableWrapperRef = useRef<HTMLDivElement>(null);

  const columnHelper = createColumnHelper<TData>() as FixedColumnHelper<TData>;

  const table = useReactTable({
    ...tableOptions,

    state: {
      ...state,
      sorting,
    },

    data,

    columns: createColumns({
      columnHelper,
      sorting,
      tableWrapperRef,
    }),

    getCoreRowModel: getCoreRowModel(),

    /** Sorting */
    getSortedRowModel: getSortedRowModel(),
    onSortingChange: setSorting,

    /** Filtering */
    getFilteredRowModel: getFilteredRowModel(),
  });

  return {
    isEmpty: table.getRowModel().rows.length === 0,
    table,
    tableWrapperRef,
  };
}

andymcgunagle avatar Sep 18 '25 23:09 andymcgunagle

// works Ok

const columnHelper = createColumnHelper<Person>()

const groupCols = [
  columnHelper.accessor(row => row.lastName, {
    id: 'lastName',
    cell: info => <i>{info.getValue()}</i>, 
    header: () => <span>Last Name</span>,
    footer: info => info.column.id,
  })
 ]

const columns = [
  columnHelper.accessor('firstName', {
    cell: info => info.getValue(),
    footer: info => info.column.id,
  }),
  columnHelper.group({
    id:'group',
    columns: groupCols
  })
]

domosedov avatar Oct 09 '25 10:10 domosedov