table icon indicating copy to clipboard operation
table copied to clipboard

fix ColumnHelper['accessor'] TValue type

Open Smona opened this issue 8 months ago • 1 comments

Fixes #4382

I ran into #4382 trying to write a generic, reusable wrapper around react-table. Looking into the issue brought me to the ColumnHelper type. I've had issues in the past when trying to transform one generic type parameter into a second, inferred generic parameter, and often it can be fixed using a distributive helper type instead. So I tried pulling out the TValue generic parameter into one, and then using that in ColumnHelper instead.

This seems to work! I'm able to create a properly typed, generic reusable wrapper component by defining the props like this:

type Columns<T> = {
    [K in keyof Required<T>]: ColumnDef<T, T[K]>;
}[keyof Required<T>][];
type ColumnsBuilder<T> = (helper: ColumnHelper<T>) => Columns<T>;

interface Props<T> {
  data: T[] | null;
  columns: ColumnsBuilder<T>;
};

And the types are compatible, with no types lost:

type Person = {
    id: number;
    firstName: string;
    lastName: string;
    age?: number;
    visits: number;
    status: string;
};

// (ColumnDef<Person, number> | ColumnDef<Person, string> | ColumnDef<Person, number | undefined>)[]
type ColumnsType = Columns<Person>;

const columns: ColumnsBuilder<Person> = (columnHelper) => [
    // Inferred as: <(p: Person) => string>(accessor: (p: Person) => string, column: DisplayColumnDef<Person, string>) => AccessorFnColumnDef<Person, string>
    columnHelper.accessor((p) => p.firstName, {
        header: 'First Name',
        cell: (info) => <strong>{info.getValue()}</strong>,
    }),
    // inferred as: <"age">(accessor: "age", column: IdentifiedColumnDef<Person, number | undefined>) => AccessorKeyColumnDef<Person, number | undefined>
    columnHelper.accessor('age', {
        header: 'Last Name',
        cell: (info) => <em>{info.getValue()}</em>,
    }),
    // inferred as: <"status">(accessor: "status", column: IdentifiedColumnDef<Person, string>) => AccessorKeyColumnDef<Person, string>
    columnHelper.accessor('status', {
        header: 'Status',
        cell: (info) => (
            <span
                style={{
                    color: info.getValue() === 'Active' ? 'green' : 'red',
                    fontWeight: 'bold',
                }}
            >
                {info.getValue()}
            </span>
        ),
    }),
],

Smona avatar Apr 25 '25 18:04 Smona

@KevinVandy any changes you would like to see here? we've been using this type in our codebase for a while now without issue.

Smona avatar Oct 23 '25 18:10 Smona