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
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
textFieldCompasWidgetComponent<TextField>andcheckboxCompasWidgetComponent<Checkbox>P.S: You should probably be using
ReactElement | nullas the return type for yourWidgetComponentfunction component. UsingReactNodewill give you trouble when rendering JSX. See, for instance: When to use JSX.Element vs ReactNode vs ReactElement?