I am currently utilizing a UI-library that offers an API for constructing tables with a structure similar to this:
type Column<Record> = {
keys: string | Array<string>;
render: (prop: any, record: Record) => React.ReactNode;
}
The library supplies the first argument to the render function by executing
column.render(record[column.keys], record)
. If column.keys
is provided as an array, it operates as a "path" within the record, like so: record[keys[0]][keys[1]]...[keys[keys.length - 1]]
. For demonstration purposes, the example below has been slightly modified using the Pick<...>
algorithm for a simplified yet functional illustration.
// Defining our record type
interface Entity {
a: string;
b: number;
c: boolean;
}
// Helper type explanation:
// GetOrPickProps<Entity, 'a'> -> Entity['a']
// GetOrPickProps<Entity, ['b', 'c']> -> Pick<Entity, 'a' | 'c'>
type GetOrPickProps<E, K extends (keyof E | Array<keyof E>)> = K extends keyof E
? E[K]
: K extends Array<infer K2>
? Pick<E, K2 & keyof E>
: never;
// Initial attempt at a Column type definition
type Column<E, K extends (keyof E | Array<keyof E>)> = {
keys: K;
render: (prop: GetOrPickProps<E, K>) => string;
}
// ...but faces issues
const columns: Array<Column<Entity, /* What should be included here??? */>> = [
{
keys: 'a',
render: a => a,
},
{
keys: ['a', 'c'],
render: ({ a, c }) => c ? a : 'something else',
}
]
If I specify 'a' | ['a', 'c']
as the second parameter in Column
, both render functions will have types of
(prop: Entity['a'] | Pick<Entity, 'a' | 'c'>) => string
.
If I make the second parameter in Column
optional (perhaps through K extends ... = unknown
), TypeScript no longer infers the type and instead defaults to using unknown
as the prop type.
Is there a way to create a type that can infer some props to limit others and also accept an explicit type parameter?
Check out the TS-playground here.