Home > Software engineering >  Is it possible to make a type of generic array's property values
Is it possible to make a type of generic array's property values

Time:07-09

I am trying to restrict values for a property (or react props in my case) based on what values are given in another property.

I have my Option interface

interface Option {
  value: string;
  label: string;
}

I then have my SelectInputProps interface

interface SelectInputProps<T extends Option> {
  options: T[];
  defaultValue: string;
}

What I want is to the make defaultValue only accept any string present in options values.

interface SelectInputProps<T extends Option> {
  options: T[];
  defaultValue: ThisShouldOnlyAcceptStringsInOptionsValues;
}

I have read this article which is close to my use case, here, but it gets types from already defined arrays. I do not know what values will be passed, hence the generic.

I did try

interface SelectInputProps<T extends Option> {
  options: T[];
  defaultValue: Array<T>[number]['value'];
}

but this only restricts it to any string, which is no different from the first snippet.

I may be going in the complete wrong direction here and there may be a simpler way, all help appreciated.

Edit

Usage in React component:

const SelectInput = <T extends Option>({
  options,
  defaultValue,
}: SelectInputProps<T>) => {
  const [value, setValue] = React.useState(defaultValue);
  const [isSelecting, setIsSelecting] = React.useState(false);

  const optionElements = options.map((option) => (
    <option value={option.value} key={option.value}>
      {option.label}
    </option>
  ));

  return (
    <div className="relative">
      <div className="flex justify-between items-center">
        <p>{options.find((op) => op.value === value)?.label}</p>
        <MdExpandMore size={'1.5rem'} />
      </div>
    </div>
  );
};

CodePudding user response:

Type inference is a tricky subject in TypeScript. The issue here is not only the generic type itself but the function declaration too. When you pass an array like [{value: "abc", label: "abc"}] to a function, TypeScript has to decide how this array literal will be interpreted.

TypeScript will default widen types like this by default. So it infers the array literal

[{value: "abc", label: "abc"}]

as

{ value: string, label: string }[]

While this technically is a perfectly valid description of the passed array literal, we lose a lot of type information. It is not possible for us to get the type of value since it just a string now.

Luckily for us, there are certain tricks to force TypeScript to infer types as narrow as we need them to be.


Let's start with the interfaces.

interface Option<V extends string> {
  value: V;
  label: string;
}

interface SelectInputProps<T extends Option<V>[], V extends string> {
  options: T;
  defaultValue: T[number]["value"];
}

We will introduce a new generic type V which will hold information about the string literal type for value.

When we declare the function, we also have to add V.

const SelectInput = <T extends Option<V>[], V extends string>({
  options,
  defaultValue,
}: SelectInputProps<T, V>) => {
  /* ... */
};

Playground


Here some test cases:

function test(){
  return (<div>
    <SelectInput options={
        [{value: "a", label: "a"}, {value: "b", label: "b"}]
      } defaultValue="wrong"></SelectInput> // Error: Type '"wrong"' is not assignable to type '"a" | "b"'
    <SelectInput options={
        [{value: "a", label: "a"}, {value: "b", label: "b"}]
      } defaultValue="a"></SelectInput>
  </div>)
}

CodePudding user response:

You need to bring the "extends" keyword outside of the <> carrots (ex. interface SelectInputProps extends Option)

  • Related