Crocsx
Crocsx

Reputation: 7609

Multiple props type, Type 'any' is not assignable to type 'never'

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

Answers (2)

ford04
ford04

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

Henry Woody
Henry Woody

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 (except never itself). Even any isn’t assignable to never.

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

Related Questions