Reputation: 7609
Sorry I don't know exactly how to name this post.
Using react and typescript : I have a component that create a set a component based on a type I pass as props :
interface FieldDispatcherProps {
children: Array<never>;
fieldId: string;
mandatory?: boolean;
mode: FieldMode;
onChange: Function;
}
const fieldTypes = {
[FieldsHelper.fields.text.value] : TextField,
[FieldsHelper.fields.textarea.value] : TextAreaField,
[FieldsHelper.fields.select.value] : SelectField,
[FieldsHelper.fields.radio.value] : RadioField,
[FieldsHelper.fields.checkbox.value] : CheckboxField,
[FieldsHelper.fields.autonum.value] : AutonumField,
[FieldsHelper.fields.number.value] : NumberField,
[FieldsHelper.fields.file.value] : FileField,
[FieldsHelper.fields.calc.value] : CalcField,
[FieldsHelper.fields.user.value] : UserField,
[FieldsHelper.fields.dslookup.value] : LookupField,
[FieldsHelper.fields.date.value] : DateField,
[FieldsHelper.fields.time.value] : TimeField,
}
const FieldDispatcher = (props: FieldDispatcherProps): JSX.Element => {
const {fieldId, mandatory, mode, onChange}: FieldDispatcherProps = props;
const field = useSelector((state: RootState) => ItemDetailsSelectors.getFieldById(state))(fieldId);
const value = useSelector((state: RootState) => ItemDetailsSelectors.getEntryValue(state))(fieldId);
return (
<div>
{((): JSX.Element => {
const FieldType = fieldTypes[field.dataType];
if(FieldType) {
return <FieldType onChange={onChange} mode={mode} mandatory={mandatory} field={field} value={value} fieldId={fieldId}/>
} else {
return <div>{value}</div>;
}
})()}
</div>
)
}
Now, Every field as the same props name, but, the prop named value
is of a different type in each component. Some expect an array, other a string, other num....
The problem I face is
const FieldType = fieldTypes[field.dataType];
if(FieldType) {
return <FieldType onChange={onChange} mode={mode} mandatory={mandatory} field={field} value={value} fieldId={fieldId}/>
} else {
return <div>{value}</div>;
}
Return the following error for the props value:
Type 'any' is not assignable to type 'never'.
If I set all my child to expect a value of type any it works, but I would like to keep the child type.
IS there a way ?
Upvotes: 0
Views: 1834
Reputation: 74490
I am not sure how to give a concrete answer here, as you are using a bunch of unknown helpers and types in your sample code. In general two alternatives come to my mind to handle value
typing:
1.) Annotate a base type to fieldTypes
, which declaresvalue
as any
:
type BaseProps<T> = {
value: T;
};
const TextField = (p: BaseProps<string>) => <span>{p.value}</span>;
const RadioField = (p: BaseProps<boolean>) => <span>{p.value}</span>;
const fieldTypes: { [K: string]: (props: BaseProps<any>) => JSX.Element } = {
a: TextField,
b: RadioField
};
So you don't need to change the child component type to expect value
of type any
, just change the fieldTypes
mapping type. Doesn't matter, what value
you retrieve by useSelector(..)
, it will fit to any
.
2.) Narrow exact value
type down via type guards/control flow:
const FieldDispatcher = (props: FieldDispatcherProps) => {
const value = useSelector( .. ) // value comes from somewhere
// field.dataType comes from somewhere
// lets assume, fieldTypes looks like {"a" : TextField , "b" : CheckboxField}
const dataType: keyof typeof fieldTypes = ...
let fieldTypeJsx: JSX.Element;
switch (dataType) {
case "a": {
// dataType narrowed to "a" here, so fieldTypes[dataType]
// contains the specific Field Comp
const FieldType = fieldTypes[dataType];
// depends on value type, if you have to type cast here
fieldTypeJsx = <FieldType value={value as string} />;
break;
}
case "b": {
// analogue to case "a"
const FieldType = fieldTypes[dataType];
fieldTypeJsx = <FieldType value={value as boolean} />;
break;
}
default: {
return <div>{value}</div>;
}
}
Upvotes: 1
Reputation: 15652
The issue here is that you declare the type of children
to be Array<never>
, so when you pass children, since at least one of the children is not of type never
, the array of children is an array of type any
, and according to the docs:
[...] no type is a subtype of, or assignable to,
never
(exceptnever
itself). Evenany
isn’t assignable tonever
.
So changing children
to be of type Array<any>
(or something more specific) will solve this problem.
However, you mention that you would like to keep the type of children. An array of type never
seems a strange type for the children
array, and another type (like Array<any>
) seems more appropriate. Alternatively, if you really need to keep children as Array<never>
, perhaps this prop should not be children
, but another prop instead.
Upvotes: 0