javascript - react-select typescript not working properly when I substitute Select with a Component that returns Select - Stack

So instead of using Select directly I thought it would be better to create a ponent named CustomSelect

So instead of using Select directly I thought it would be better to create a ponent named CustomSelect which will return Select with some properties prefilled.

The issue is that now when I use the CustomSelect ponent with property isMulti={false} typescript does not automatically understands that OnChange property should be of type

(newValue: SingleValue<IOption>, actionMeta: ActionMeta<IOption>) => void

instead of

(newValue: SingleValue<IOption> | MultiValue<IOption>, actionMeta: ActionMeta<IOption>) => void

Why is this happening and is there any good workaround?

CustomSelect.tsx

import Select, {
  ponents,
  OptionProps,
  ValueContainerProps,
  Props as SelectProps,
} from "react-select";
import { IOption } from "../../Table/Types";
import "./CustomSelect.scss";

//type IOption = {
//  value: any;
//  label: string;
//  icon?: string;
//};

const { Option: OptionCointainer, ValueContainer } = ponents;

function OptionWithIcon(props: OptionProps<IOption>) {
  const {
    label,
    data: { icon },
  } = props;
  return (
    <OptionCointainer {...props} className="selectOption">
      {!!icon && <img src={icon} alt={label} />}
      {label}
    </OptionCointainer>
  );
}

// react-select is buggy when you try to override rendered value ponent
// children is being used as a workaround
function ValueWithIcon(props: ValueContainerProps<IOption>) {
  const { getValue, children } = props;
  const { label, icon } = getValue()[0];
  return (
    <ValueContainer {...props} className="selectValue">
      <>
        {!!icon && <img src={icon} alt={label} />}
        {label}
        <span className="hiddenAbsolute">{children}</span>
      </>
    </ValueContainer>
  );
}

function CustomSelect(props: SelectProps<IOption>) {
  const { options } = props;
  return (
    <Select
      isDisabled={options ? options.length < 2 : true}
      ponents={{
        Option: OptionWithIcon,
        ValueContainer: ValueWithIcon,
      }}
      {...props}
    />
  );
}

export default CustomSelect;

Filters.tsx

      <Select
        id="currencyFrom"
        isMulti={false}
        value={state.fromCurrency}
        // if I hover on change type is (newValue: SingleValue<IOption>, actionMeta: ActionMeta<IOption>) => void
        onChange={onValueChange("fromCurrency")}
        options={someOptions}
        isSearchable={false}
      />
      <CustomSelect
        id="currencyTo"
        isMulti={false}
        value={state.toCurrency}
        // if I hover on change type is (newValue: SingleValue<IOption> | MultiValue<IOption>, actionMeta: ActionMeta<IOption>) => void
        onChange={onValueChange("toCurrency")}
        options={someOptions}
        isSearchable={false}
      />

So instead of using Select directly I thought it would be better to create a ponent named CustomSelect which will return Select with some properties prefilled.

The issue is that now when I use the CustomSelect ponent with property isMulti={false} typescript does not automatically understands that OnChange property should be of type

(newValue: SingleValue<IOption>, actionMeta: ActionMeta<IOption>) => void

instead of

(newValue: SingleValue<IOption> | MultiValue<IOption>, actionMeta: ActionMeta<IOption>) => void

Why is this happening and is there any good workaround?

CustomSelect.tsx

import Select, {
  ponents,
  OptionProps,
  ValueContainerProps,
  Props as SelectProps,
} from "react-select";
import { IOption } from "../../Table/Types";
import "./CustomSelect.scss";

//type IOption = {
//  value: any;
//  label: string;
//  icon?: string;
//};

const { Option: OptionCointainer, ValueContainer } = ponents;

function OptionWithIcon(props: OptionProps<IOption>) {
  const {
    label,
    data: { icon },
  } = props;
  return (
    <OptionCointainer {...props} className="selectOption">
      {!!icon && <img src={icon} alt={label} />}
      {label}
    </OptionCointainer>
  );
}

// react-select is buggy when you try to override rendered value ponent
// children is being used as a workaround
function ValueWithIcon(props: ValueContainerProps<IOption>) {
  const { getValue, children } = props;
  const { label, icon } = getValue()[0];
  return (
    <ValueContainer {...props} className="selectValue">
      <>
        {!!icon && <img src={icon} alt={label} />}
        {label}
        <span className="hiddenAbsolute">{children}</span>
      </>
    </ValueContainer>
  );
}

function CustomSelect(props: SelectProps<IOption>) {
  const { options } = props;
  return (
    <Select
      isDisabled={options ? options.length < 2 : true}
      ponents={{
        Option: OptionWithIcon,
        ValueContainer: ValueWithIcon,
      }}
      {...props}
    />
  );
}

export default CustomSelect;

Filters.tsx

      <Select
        id="currencyFrom"
        isMulti={false}
        value={state.fromCurrency}
        // if I hover on change type is (newValue: SingleValue<IOption>, actionMeta: ActionMeta<IOption>) => void
        onChange={onValueChange("fromCurrency")}
        options={someOptions}
        isSearchable={false}
      />
      <CustomSelect
        id="currencyTo"
        isMulti={false}
        value={state.toCurrency}
        // if I hover on change type is (newValue: SingleValue<IOption> | MultiValue<IOption>, actionMeta: ActionMeta<IOption>) => void
        onChange={onValueChange("toCurrency")}
        options={someOptions}
        isSearchable={false}
      />

Share Improve this question edited Sep 11, 2022 at 18:34 prof chaos asked Sep 11, 2022 at 11:00 prof chaosprof chaos 4444 silver badges20 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 5

The SelectProps type you are trying to adapt is a generic type with 3 type arguments (defined here). Because you provide a type for the first argument, inference is disabled the other 2 arguments and Typescript sets them to their default values instead.

If you need inference to work, you cannot provide partial type arguments, I can see 2 solutions:

  • Get rid of the Option argument in SelectProps<Option> and let the Option type be inferred as well
function CustomSelect(props: SelectProps) {
  • If you need to enforce a specific Option type, create another generic type for your props that only takes the 2 last arguments and let typescript infer these:
type CustomSelectProps<
    IsMulti extends boolean = boolean,
    Group extends GroupBase<Option> = GroupBase<Option>
> = SelectProps<Option, IsMulti, Group>;

…

function CustomSelect(props: CustomSelectProps) {

Also, because you want this type of inference to happen when using the ponent, you should also make your ponent generic, applying the same logic to the StateManagedSelect ponent type (defined here).

This gives us the following ponent type definition, using the CustomSelectProps:

function CustomSelect<
    IsMulti extends boolean = false,
    Group extends GroupBase<IOption> = GroupBase<IOption>
>(props: SelectProps<IOption, IsMulti, Group>) {

Updated codesandbox: you'll see I had to apply the same logic to your other ponents as well, as all the types depend on each other.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745488421a4629875.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信