Given the following types and components:
interface Widget { type: string; }
interface TextField extends Widget { type: 'TextField'; textValue: string; }
interface Checkbox extends Widget { type: 'Checkbox'; booleanValue: boolean; }
type Props<W extends Widget> = { widget: W; }
type WidgetComponent<W extends Widget> = (props: Props<W>) => React.ReactNode;
const TextfieldComponent: WidgetComponent<TextField> = ({ widget }) => { return <input type="text" /> };
const CheckboxComponent: WidgetComponent<Checkbox> = ({ widget }) => { return <input type="checkbox" />};
and the following predicates:
const isTextfield = (widget: Widget): widget is TextField => widget.type === 'TextField';
const isCheckbox = (widget: Widget): widget is Checkbox => widget.type === 'Checkbox';
How can I type properly the given function?
const factory = <W extends Widget>(widget: W): WidgetComponent<W> | null => {
if (isTextfield(widget)) {
return TextfieldComponent;
} else if (isCheckbox(widget)) {
return CheckboxComponent;
}
return null;
}
TypeScript does not like that and it tells me:
Type 'WidgetComponent<TextField>' is not assignable to type 'WidgetComponent<W>'.
Type 'W' is not assignable to type 'TextField'.
Property 'textValue' is missing in type 'Widget' but required in type 'TextField'.(2322)
I understand why TypeScript is giving me this error but I don’t know which mechanism should I use to give TypeScript the proper type constraints.
You can see it in action on the TypeScript playground
2
Answers
You can achieve what you want using function overloads.
The solution is hard to scale if you have many types of
Widget
, as you need an overload for each of them, but it should be fine for your case.You can test with:
Your IDE type hint should correctly identify
textFieldComp
asWidgetComponent<TextField>
andcheckboxComp
asWidgetComponent<Checkbox>
P.S: You should probably be using
ReactElement | null
as the return type for yourWidgetComponent
function component. UsingReactNode
will give you trouble when rendering JSX. See, for instance: When to use JSX.Element vs ReactNode vs ReactElement?