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.