`getValue` fails to infer correct type when `columnHelper.accessor` defined within `columnHelper.group`
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.
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,
};
}
// 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
})
]