I’ve got DataGrid component in my React project. I want it to be type safe, so I made generic type T for data and K as keyof T for column, but when I want to create columns Typescript doesn’t know the type of property instead it gives type as union.
Sample interfaces:
interface DataGridProps<T extends object> {
data: T[];
columns: Array<ColumnProps<T, keyof T & string>>;
idField: keyof T & string;
}
interface ColumnProps<T, K extends keyof T> {
field: K,
sorter?: (a: T[K], b: T[K]) => number;
formatter?: (value: T[K]) => string;
}
Here is playground with problem:
TS Playground
I tried:
Pass columns as list inline (not working)
const gridProps: DataGridProps<Person> = {
data: [{id: 1, name: "Test", active: true}],
columns: [{
field: "id",
sorter: (a: number, b: number) => 1,
}],
idField: "id"
}
Create list of columns (not working)
const columns: ColumnProps<Person, keyof Person>[] =
[{field: "id", sorter: (a: number, b: number) => 1}]
Creating individual column with generic works, but is there any other solution that not require that much code?
const column1 : ColumnProps<Person, "id"> = {
field: "id",
sorter: (a: number, b: number) => 1,
}
2
Answers
The problem is with the signature of the
ColumnProps.sorter
method’s signature during the assignment.Fix:
Explanation:
The reason why your code doesn’t work is when you say
keyof Person
, what you’re really telling Typescript is that it can be any of the types defined in the person interface. In addition to that, when definingColumnProps
, you said that sorter takes arguments that are of typeT[K]
, which isstring | number | boolean
.So in your case, it can be a
number
,string
orboolean
since you define your Person interface asBut when you defined one instance, you said I will target the
id
key. So, Typescript can now look at the Person interface and understand – alright, sinceid
is of number type, I’ll let you assign a number.So, if you wanted to handle only number types to be sorted, then this would be the way to go:
But since you want a generic
sorter
method, you would have to be consistent with the signature when using the type for which you defined the generic. Hence the fix I wrote at the start of the answer.You are actually really close to the desired solution. The problem is in
ColumnProps
with theK
parameter. When you useColumnProps
inDataGridProps
you passK
askeyof T & string
which is resulted in a union, thus in thesorter
T[K]
is resolved to all possible values.Example:
Since, we would expect to get:
this method doesn’t work and the solution is distributive conditional types. Basically, we need to check if
K
extends something to distribute it, but we must be sure that this condition will be always true. Possible ones areK extends K
orK extend any
, .etc.Testing:
Looks great! Now, let’s apply the same logic to
ColumnProps
:Usage:
playground