Im building a DataGrid component. I want to achieve a behavior were based on the row object type, I can infer the fields that can be declared as columns.
So the use case of the component would be:
const users: User[] = [
{name: 'John', isActive: true, id: 1},
{name: 'Mike', isActive: false, id: 2}
]
return <DataGrid rows={users} columns={[
{field: 'name', formatter: (value) => value.toUpperCase()}, // value should be string
{field: 'id', formatter: (value) => value}, // value should be number
{field: 'isActive', formatter: (value) => value} // value should be boolean
]}/>
And Im typing the DataGrid props as
interface ColumnDefinition<T extends object, K extends keyof T> {
field: K
formatter(value: T[K]) => string
}
interface DataGridProps<T, K> {
rows: T[]
columns: ColumnDefinition<T, K>[]
}
The current behavior is that the field
attribute is correctly inferring all the keys from user
, but the value on the formatter callback is inferring the union of all the posibles values on user, like string | number | boolean
What am I missing here?
you can solve your problem by making your type like this :
type ColumnDefinition<T extends object> = {
[K in keyof T]: {
field: K;
formatter: (value: T[K]) => string;
};
}[keyof T];
interface DataGridProps<T extends object> {
rows: T[];
columns: ColumnDefinition<T>[];
}
With this solution, ColumnDefinition becomes a union and typescript discriminates this using the value of the field
attribute.
I got my inspiration from this: https://github.com/microsoft/TypeScript/issues/36444#issuecomment-578572999