Generic type with dynamic key

37 Views Asked by At

Given the following, the parameter value of the render method is typed as string | number.

How can I make sure that it gets typed as T[dataIndex], which is string in case dataIndex: 'name', and number in case dataIndex: 'id'?

import * as React from 'react'

type Col<T extends object, K extends keyof T> = {
    title: string;
    dataIndex: K;
    render: (value: T[K]) => React.ReactNode;
};

function Tab<T extends object, K extends keyof T = keyof T>(list: T[], cols: Col<T, K>[]): JSX.Element {
    return (
        <table>
            <thead>
                <tr>
                    {cols.map(col => (
                        <th key={col.title}>{col.title}</th>
                    ))}
                </tr>
            </thead>
            <tbody>
                {list.map((item, i) => (
                    <tr key={i}>
                        {cols.map(col => (
                            <td key={col.title} children={col.render(item[col.dataIndex])} />
                        ))}
                    </tr>
                ))}
            </tbody>
        </table>
    );
}

Tab(
    [
        { id: 1, name: 'Hello' },
        { id: 2, name: 'World' }
    ],
    [
        {
            title: 'Name',
            dataIndex: 'name',
            render: value => <span>{value.length}</span>
        },
        {
            title: 'Id',
            dataIndex: 'id',
            render: value => <span>{value}</span>
        }
    ]
);

Playground link

1

There are 1 best solutions below

6
Titian Cernicova-Dragomir On BEST ANSWER

You need an extra type parameter to the component to capture the actual keys that are actually used as a tuple. You can then use a mapped type to map it to the array of columns, Typescript will be able to follow the reverse mapping and infer K from the array and then go back and correctly type the parameters.

type ColMapping<T extends object, K extends Record<number, keyof T>> = {
    [P in keyof K ]: Col<T, K[P] & keyof T>
}


function Tab<T extends object, K extends (keyof T)[]>(list: T[], cols: ColMapping<T, K>): JSX.Element {
//...
}

Playground Link