To extend the react-select interface property in typescript

Viewed 5067

I have customize a react-select drop-down component for my react-project.When i try to extend the interface using React.HTMLAttributes<HTMLSelectElement | HTMLInputElement>. It shows "No overload matches this call". I have tried to extend different attributes to get the default values like id, label, name, placeholder, etc. How can I get the default properties inside the props? Did anyone experience any similar issue?

sample code:

import * as React from "react";

import ReactSelect from 'react-select';

export interface SelectProps extends React.HTMLAttributes<HTMLSelectElement | HTMLInputElement> {
    options: Array<any>;
    isMulti: boolean;
    isDisabled: boolean;

};

const Select: React.FC<SelectProps> = (props: SelectProps) => {

    return (<div>
        <label htmlFor={props.id}>{props.label}</label>
        <div>
            <ReactSelect
                {...props}
            />
        </div>
    </div>)
}
export default Select;

After using the above code, I am not able to get props.id or props.name, etc.

3 Answers

Edit:

React-Select v5 now natively supports TypeScript, therefore some type changes have been made (details).

Here's an updated example for v5 (react-select/async), similar to the original one with v4:

import ReactSelectAsync, { AsyncProps } from "react-select/async";
import { GroupBase } from "react-select";

interface CustomSelectAsyncProps {
  additionalCustomProp: number;
}

function SelectAsync<
  OptionType,
  IsMulti extends boolean = false,
  GroupType extends GroupBase<OptionType> = GroupBase<OptionType>
>({
  additionalCustomProp,
  ...props
}: AsyncProps<OptionType, IsMulti, GroupType> & CustomSelectAsyncProps) {
  return <ReactSelectAsync {...props} />;
}

export default SelectAsync;

Codesandbox

Original Answer:

This is documented on the official react-select documentation.

Quote:

Wrapping the Select component Oftentimes the Select component is wrapped in another component that is used throughout an app and the wrapper should be just as flexible as the original Select component (i.e., allow for different Option types, groups, single-select, or multi-select). In order to provide this flexibility, the wrapping component should re-declare the generics and forward them to the underlying Select. Here is an example of how to do that:

function CustomSelect<
  Option,
  IsMulti extends boolean = false,
  Group extends GroupBase<Option> = GroupBase<Option>
>(props: Props<Option, IsMulti, Group>) {
  return (
    <Select {...props} theme={(theme) => ({ ...theme, borderRadius: 0 })} />
  );
}

It also explains how to extend the props with additional custom props:

You can use module augmentation to add custom props to the Select prop types:

declare module 'react-select/dist/declarations/src/Select' {
  export interface Props<
    Option,
    IsMulti extends boolean,
    Group extends GroupBase<Option>
  > {
    myCustomProp: string;
  }
}

But I personally prefer to just add a custom interface using the & character with a custom interface to add custom props (example with ReactSelectAsync, see ... & CustomSelectAsyncProps):

interface CustomSelectAsyncProps {
  additionalCustomProp: number;
}

function SelectAsync<
  OptionType extends OptionTypeBase,
  IsMulti extends boolean = false,
  GroupType extends GroupTypeBase<OptionType> = GroupTypeBase<OptionType>
>({
  additionalCustomProp,
  ...props
}: Props<OptionType, IsMulti, GroupType> & CustomSelectAsyncProps) {
  return (
    <ReactSelectAsync {...props} />
  );
}

You can use like this:

import React from 'react'
import { omit } from 'lodash';
import Select, { GroupBase, Props } from 'react-select';

type SelectProps<Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>> = Props<Option, IsMulti, Group> & {
    label?: string;
    id?: string;
}

const SearchableSelect = <
    Option,
    IsMulti extends boolean = false,
    Group extends GroupBase<Option> = GroupBase<Option>
>(props: SelectProps<Option, IsMulti, Group>) => {
    const id = props.id || Math.random().toString()
    const reactSelectProps = omit(props, ['label', 'className'])

    return (
        <div>
            {props.label && (
                <label htmlFor={id} className="block text-sm font-medium text-gray-700">
                    {props.label}
                </label>
            )}
            <Select
                {...reactSelectProps}
                theme={(theme) => ({
                    ...theme,
                    colors: {
                        ...theme.colors,
                        primary: "var(--primary-500)",
                    },
                })}
            />
        </div>
    );
}


export default SearchableSelect

You can import the Props type directly from the react-select package. You probably want to extend it in order to require that label and id are both defined.

import React from "react";
import ReactSelect, { Props } from "react-select";

type SelectProps = Props & {
  id: string;
  label: string;
};

const Select: React.FC<SelectProps> = (props) => {
  return (
    <div>
      <label htmlFor={props.id}>{props.label}</label>
      <div>
        <ReactSelect {...props} />
      </div>
    </div>
  );
};
Related