Apologies, I'm sure this has been answered somewhere, but I'm not sure what to google. Please do edit my question if the terms in the title are wrong.
I have something like this:
type RowData = Record<string, unknown> & {id: string};
type Column<T extends RowData, K extends keyof T> = {
key: K;
action: (value: T[K], rowData: T) => void;
}
type RowsAndColumns<T extends RowData> = {
rows: Array<T>;
columns: Array<Column<T, keyof T>>;
}
And TypeScript should be able to infer the types of the action
functions, by examining the shape of the rows, and what key
value has been given to the column:
ie:
function myFn<T extends RowData>(config: RowsAndColumns<T>) {
}
myFn({
rows: [
{id: "foo",
bar: "bar",
bing: "bing",
bong: {
a: 99,
b: "aaa"
}
}
],
columns: [
{
key: "bar",
action: (value, rowData) => {
console.log(value);
}
},
{
key: "bong",
action: (value, rowData) => {
console.log(value.a); //Property 'a' does not exist on type 'string | { a: number; b: string; }'.
}
}
]
});
Playground Link
The problem is, TypeScript seems to be deriving the type of value (value: T[K]
) as 'the types of all accessible by all keys of T' rather than using just the key provided in the column object.
Why is TypeScript doing this, and how can I solve it?
What would make some good answer is defining some specific terms and concepts.
I imagine I want to change my K extends keyof T
to be something like 'K is a keyof T, but only one key, and it never changes'.
If you expect the keys of T
to be a union of literals like "bong" | "bing" | ...
(and not just string
), then you can express a type which is itself the union of Column<T, K>
for each key K
in keyof T
.
I usually do this via immediately indexing (lookup) into a mapped type:
type SomeColumn<T extends RowData> = {
[K in keyof T]-?: Column<T, K>
}[keyof T]
but you can also do it via distributive conditional types:
type SomeColumn<T extends RowData> = keyof T extends infer K ?
K extends keyof T ? Column<T, K> : never : never;
Either way, your RowsAndColumns
type would then use SomeColumn
instead of Column
:
type RowsAndColumns<T extends RowData> = {
rows: Array<T>;
columns: Array<SomeColumn<T>>;
}
And this makes your desired use case work as expected without compile errors:
myFn({
rows: [
{
id: "foo",
bar: "bar",
bing: "bing",
bong: {
a: 99,
b: "aaa"
}
}
],
columns: [
{
key: "bar",
action: (value, rowData) => {
console.log(value);
}
},
{
key: "bong",
action: (value, rowData) => {
console.log(value.a);
}
},
]
});
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With