I encountered a really weird problem and I'm not sure if the problem is related to badly written types or it's a weird typescript thing.
So I have this props type:
type SelectAnswersProps = (
| {
onChange: Dispatch<SetStateAction<MultiSelectAnswerOptionsType | null>>;
value: MultiSelectAnswerOptionsType;
isMultiSelect: true;
}
| {
onChange: Dispatch<SetStateAction<SelectAnswerOptionsType | null>>;
value: SelectAnswerOptionsType;
isMultiSelect: false;
}
) & {
displayMode?: SelectAnswerOptionsType['displayMode'];
disabled?: boolean;
};
And then I'm trying to force typescript to read what type is being passed to onChange
method.
if (isMultiSelect) {
onChange({
answers,
isHeadless,
});
} else {
onChange({
answers,
displayMode,
});
}
So the first onChange is correct as it shows its type as onChange: (value: SetStateAction<MultiSelectAnswerOptionsType>) => void
but the second one I'd expect to be like in the types specified in props with isMultiSelect
being false
but instead type of 2nd onChange is as follows: onChange: (value: SetStateAction<MultiSelectAnswerOptionsType> & SetStateAction<SelectAnswerOptionsType>) => void
so kinda mix of both even though from props types you would say it's either one or another.
I reckon I've done something silly here. Really appreciate all your help.
Thanks
You have defined the props
to be (simplified for readability) :
props: { onChange: MultiSelectDispatcher, ... } | { onChange: SelectDispatcher, ... }
That means, the props
can only be the one or the other, and Typescript doesn't know which one it is, and infers that from the usage:
if( isMultiSelect ){
onChange({ answers, isHeadless }); // <-- TS thinks: probably the "MultiSelect" one
} else {
onChange({ answers, displayMode }); // <-- TS already decided "MultiSelect" earlier
}
So Typescript decides which type the whole props
object probably is at some point, and sticks to that decision from there on.
As a solution you might be able to use type guards (maybe not this easy, see the "But..." below) , like:
const guardMultiSelectProps = ( theProps: unknown ): theProps is MultiSelectProps => {
return theProps.isMultiSelect;
}
if( guardMultiSelectProps( theProps ) ){
onChange({ answers, isHeadless }); // <-- TS thiks: probably the "MultiSelect" one
} else {
onChange({ answers, displayMode }); // <-- TS already decided "MultiSelect" earlier
}
But also Typescript might have inferred the type of the props even earlier, or will "suddenly" do that if you make some changes to your code later.
I suggest, if possible, not to define two alternatives for the props
object, but instead define only one type for the props
, with two alternative onChange
handlers. (The React props should stay consistent anyway)
Eg:
props: {
onChange: MultiSelectDispatcher | SelectDispatcher,
...
}
And also use a type guard, like:
// Note: This type guard works, but is not inherently type safe.
const guardMultiSelectOnChange = ( onChange: unknown, multiSelect: boolean ): onChange is MultiSelectDispatcher => {
return multiSelect;
}
if( guardMultiSelectOnChange( onChange, isMultiSelect )) {
onChange({ answers, isHeadless });
} else {
onChange({ answers, displayMode });
}
My last type guard guardMultiSelectOnChange
makes another problem obvious:
We always assumed that the onChange
function is the correct one if isMultiSelect
is one or the other. But nothing prevents someone to pass isMultiSelect: false
and the MultiSelectDispatcher
.
It would be better to type guard the function signature itself, instead of just stating onChange is MultiSelectDispatcher
if some other property ( isMultiSelect
) has some value.
But that will be a somewhat larger refactoring, outside the scope of this question, I guess.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.