Issue
I have the following class and interface definition:
import { Component } from 'react';
type KeysMatching<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}[keyof T];
interface SearchListProps<T extends object, K extends KeysMatching<T, string>> {
data: T[];
sort_and_filter_key: K;
}
class SearchList<ItemDataT extends object, K extends KeysMatching<ItemDataT, string>> extends Component<SearchListProps<ItemDataT, K>> {
constructor(props: SearchListProps<ItemDataT, K>) {
super(props);
const value = props.data[0][props.sort_and_filter_key]; // ItemDataT[K] -> should be string
}
}
// exmple case:
interface DataExample {
name: string,
city: string,
id: number,
content: object
// etc
}
const example_data: DataExample[] = [
{
name: "asd",
city: "bcn",
id: 1,
content: {}
},
{
name: "dkg",
city: "nld",
id: 2,
content: {}
}
];
<SearchList data={example_data} sort_and_filter_key={"city"}></SearchList>
As you can see commented in the code typescript thinks that value is of type ItemDataT[K]
but it should be string
.
Why? because the property sort_and_filter_key
should only allow keys of which the value is a string due to the following generic expression: K extends KeysMatching<T, string>
. But typescript doesn't seem to understand this.
The autocomplete does seem to understand see image below:
Since this picture clear only allows the two keys that are guaranteed to be strings.
How do i make tsc understand this also?
If anything is unclear please let me know so i can modify the question.
Solution
It seems the issue is a limitation of Typescript inference capabilities with respect to generic types - Typescript does not keep track all inferable constraints. Definitely there are some tradeoffs in doing so.
For a concrete type the inference works as expected:
const props: SearchListProps<DataExample> = {
data: example_data,
sort_and_filter_key: 'city',
};
const ok_string = props.data[0][props.sort_and_filter_key];
In case of a generic type we need to manually give a hint to TS engine what exactly we are interested in:
interface SearchListProps<T> {
data: (T & Record<KeysMatching<T, string>, string>)[];
sort_and_filter_key: KeysMatching<T, string>;
}
Interestingly we can intersect T
with anything else - for example Record<KeysMatching<T, string>, number>
, but then we are not able to initialise SearchListProps
with a concrete type.
A fixed and simplified version: https://stackblitz.com/edit/react-ts-x4jdez
Btw. props.data[0][props.sort_and_filter_key]
is unsafe (noUncheckedIndexedAccess].
Answered By - artur grzesiak
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.