Issue
I can not understand why ts complains about return option[optionTextKey]
with this message:
Type TTextKey cannot be used to index type TOption
Code:
type Props<
TTextKey,
TOption extends (TTextKey extends string ? { [key in TTextKey]: string; } : string),
> = (
{
optionTextKey: TTextKey;
options: TOption[];
}
|
{
options: string[];
optionTextKey?: never;
}
) & {
name: string;
placeholder: string;
};
function LinkDropdown<
TTextKey,
TOption extends (TTextKey extends string ? { [key in TTextKey]: string; } : string),
>({
options,
optionTextKey,
name,
placeholder,
}: Props<TTextKey, TOption>): ReactElement
// some code
const getOptionText = (option: TOption, optionTextKey: TTextKey) => {
if ('string' === typeof option) return option;
// ts complains here
return option[optionTextKey];
};
// some code
}
This message is gone if I make TOption
this way:
TOption extends TTextKey extends { [key in TTextKey]: string; }
upd
How can I fix it?
Solution
The problem is that TypeScript cannot currently use control flow analysis (e.g., an if
/else
check) to narrow or re-constrain generic type parameters. In
const getOptionText = (option: O, optionTextKey: K) => {
if ('string' === typeof option) return option;
return option[optionTextKey];
};
you apparently expect checking whether option
is a string
to have some effect on the type parameter O
, which should then presumably also have an effect on the type parameter K
. But TypeScript doesn't work this way. Checking option
only affects the type of option
, not O
. Indeed you cannot assume that O
is a subtype of string
, because it might be wider. For example, it could be that option
is string
, but O
is actually the union type string | {a: string}
. Currently there's nothing good TypeScript can do to O
for such checks, so it does nothing. And then you get errors when O
and K
are still unaffected in the line option[optionTextKey]
.
There are a number of open requests in GitHub trying to get some better support. Probably the most relevant one for your question is microsoft/TypeScript#33912, which is specifically about generic conditional types, and since your O
is constrained to a conditional type that depends on the generic K
, this missing feature is at least related. But even if this were implemented it might not solve the issue you're having. For now, at least, if you want to use control flow analysis to work, you will have to switch away from generics and towards unions.
So, one way to make your code compile is to replace the generic O
with a relevant concrete union type:
const getOptionText = (
option: string | { [P in K & string]: string },
optionTextKey: K & string
) => {
if ('string' === typeof option) return option;
return option[optionTextKey];
};
Now since option
is of the union type string | { ⋯ }
, a check of typeof option
can narrow option
to either side of the union. If it's the string
side, then return option
is a string
. If it's the object side, then option
can be indexed with optionTextKey
. Note that I had to make the type of optionTextKey
the intersection type K & string
to allow it to be used as a key type, since your K
is not already constrained as such. It's quite possible that there are a lot of changes one could make to improve the code example here, but I consider this out of scope for the question as asked. The change I've shown here is the minimal one I can think of which works.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.