1

I have a dropdown component with a callback onSelect:(option: string) => void

I'm using it as a sort picker in one application, where the options are of enum type:

enum SORT_OPTIONS {
  LATEST = 'LATEST',
  OLDEST = 'OLDEST'
}
const onSortSelect = (val: SORT_OPTIONS) => {...}
<MyDropdown onSelect={onSortSelect} ... />

And typescript complains:

Type '(val: SORT_OPTIONS) => void' is not assignable to type '(option: string) => void'.
  Types of parameters 'val' and 'option' are incompatible.
    Type 'string' is not assignable to type 'SORT_OPTIONS'.  TS2322

It's a bit counter-intuitive but makes sense, since in MyDropdown a string option will be passed to the onSortSelect callback, which requires an enum value.

Question is what is the best way to fix the types here? IMO both functions are typed correctly, onSelect should accept any string since MyDropdown can be used in any context. onSortSelect should only accept SORT_OPTIONS.

Currently I'm casting the type of onSortSelect: <MyDropdown onSelect={onSortSelect as (val:string) => void} ... /> but it feels quite verbose

Xun Yang
  • 4,209
  • 8
  • 39
  • 68
  • the `MyDropdown` is a third party components ? if not why don't you just change the callback type ? – Fahd Lihidheb Aug 05 '20 at 09:50
  • Because MyDropdown is a generic component that should accept a callback that can use any string, not just SORT_OPTIONS. As I described, I don't think the typing is wrong with either function. – Xun Yang Aug 05 '20 at 10:06
  • if it is generic why isn't it of type `(option: any) => void` ? PS: there is no solution here as it is a clearly a question of preferences. – Fahd Lihidheb Aug 05 '20 at 10:10

2 Answers2

0

Your callback deals with onSelect:(option: string) => void

This means that it passes any string to the handle, like: hello, test, and so on.

But your const onSortSelect = (val: SORT_OPTIONS) => {...} expects only subset of strings: LATEST, OLDEST.

So, TypeScript warns you that you can't pass the whole set to a function that expects subset.

To fix the issue, you need to update your onSelect handler to:

onSelect:(option: SORT_OPTIONS) => void
Drag13
  • 5,859
  • 1
  • 18
  • 42
0

I managed to fix the types with Generics, no typescript error

enum SORT_OPTIONS {
  NEWEST='1',
  OLDEST='2'
}

interface DropdownProps<T extends string> {
  onSelect: (val: T) => void;
  options: T[];
}

const Dropdown = <T extends string>(props: DropdownProps<T> & {children?: React.ReactNode}) =>{
  const {onSelect, options} = props;
  return <select onChange={(evt) => onSelect(evt.target.value as T)}>
    {options.map(option => <option>{option}</option>)}
  </select>

};



const TestComp:React.FC = () => {
  const onSelect = (val: SORT_OPTIONS) => {
    switch (val) {
      case SORT_OPTIONS.OLDEST:
        console.log('old');
        break;
      case SORT_OPTIONS.NEWEST:
        console.log('new');
        break;
    }
  };
  const onSelect2 = (val: string) => {
    console.log(val);
  };
  return <>
    <Dropdown
      options={[SORT_OPTIONS.NEWEST, SORT_OPTIONS.OLDEST]}
      onSelect={onSelect}
    />
    <Dropdown
      options={['aaa', 'bbb']}
      onSelect={onSelect2}
    />
    </>
};

Note that

  1. I can't type Dropdown with the regular React.FC, see the discussion here: How to use generics in props in React in a functional component?
  2. I have to do a type casting to the Generic type.

Please comment if you find a different conclusion from the link.

Xun Yang
  • 4,209
  • 8
  • 39
  • 68