I have generic component ( with types below):
const Search = <T extends object | number | string, K extends T extends object ? keyof T : T>(props: SearchProps<T,K>) => {
const { data, searchKey } = props;
return (
<div> Test </div>
)
};
export type SearchProps<T, K> = {
type: SearchType;
setSearcheData: Function;
data: T[];
searchKey: K;
};
U should be able to use it as in exmample:
const dataObject: dataObjectType = [
{
name: '1',
id: 1,
},
{
name: '2',
id: 2,
}
];
const dataString = ['1', '2'];
<Search
data={dataString}
searchKey={'string'}
type={SearchType.SingleIcon}
setSearcheData={()=>{}}
/>
I should be able to pass data
as Array and searchKey
as if T is object, key of it, or just a type.
But when i try to do something like
data[0][`${searchKey}`]
i got an TS error.
I tried something like
typeof data[0] === 'object' or Object.keys(data[0]).find(key => key === `${searchKey}`)
But it doesnt work.
Any ideas?
I assume the problem is happening inside the implementation of Search()
.
There are a number of issues here that are preventing the compiler from verifying that what you're doing is type safe. In the end, you will most likely need to give up on such automatic type safety verification, and tell the compiler not to worry about it by using a type assertion.
Here are the issues as I see them:
Inside the implementation of Search()
, the type of props
depends on the unspecified generic type parameters T
and K
; the compiler can reason quite well about type safety for specified types, but when it has unspecified generics like T
and K
, it isn't as clever. Specifically, control flow analysis like type guarding, does not affect generic type parameters (see microsoft/TypeScript#24085 for more information). So you can test props
as much as you want, and it won't let the compiler understand that T
should be considered assignable to object
(as opposed to object | string | number
) or that K
should be considered assignable to keyof T
.
Inside the implementation of Search()
, the generic constraint for K
is T extends object ? keyof T : T
, a conditional type which itself depends on an unspecified generic type parameter T
. The compiler is especially bad at being able to manipulate such "unresolved conditional types". (There are lots of GitHub issues where this is the underlying problem; you can look at microsoft/TypeScript#35257 for example.) I mean, even this doesn't work:
function foo<T>(t: T) {
const x: number extends T ? T : T = t; // error!
// Type 'T' is not assignable to type 'number extends T ? T : T'
}
If the compiler can't verify that number extends T ? T : T
is essentially the same as T
, there's no way it will be able to deal with the more complex case here.
You've broken out the properties of props
into two separate variables data
and searchKey
. Once you do this, the compiler completely loses track of the fact that they are correlated to each other. (See microsoft/TypeScript#30581 for more information) That means that when you check, say typeof data[0] === "object"
, the compiler does not realize that it has any implication on the type of searchKey
. It treats data
and searchKey
as completely independent variables.
In order to determine whether K
is a keyof T
or a T
, you need to check whether data[0]
or props.data[0]
is of type object
, string
, or number
. These types are not "singleton types" and so the compiler will not treat props
as a discriminated union, even if you could write its type like SearchProps<T, keyof T> | SearchProps<string | number, string | number>
. So there's no great way to take advantage of the union filtering that you get when checking properties of discriminated unions.
All of those put together means that the compiler is really not equipped to verify type safety for you. In cases like this, where you know more than the compiler, you should probably use a type assertion:
const Search = <T extends object | number | string, K extends T extends object ? keyof T : T>(
props: SearchProps<T, K>
) => {
const { data, searchKey } = props;
if (typeof data[0] === 'object') {
data[0][searchKey as keyof T]
} else {
data.indexOf(searchKey as T);
}
};
In the above, searchKey as keyof T
and searchKey as T
are the type assertions where you tell the compiler what you know about searchKey
and it just believes you and moves on. That should suppress the errors. Note that this places the burden of verifying type safety on you, and you should take this responsibility seriously, since you can't expect a warning to appear if you make some change that invalidates your assumptions. For example, you can change (typeof data[0] === 'object')
to (typeof data[0] !== 'object')
and the compiler won't notice. So be careful.
Okay, hope that helps; good luck!