admin管理员组

文章数量:1430549

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.

本文标签: