2

I created the SelectProps interface!

export interface SelectProps {
  value: string
  options: string[]
  onChange: (value: any) => void
}

I created react component!

<Select
  value="red"
  options={['dark', 'white']}
  onChange={(value) => console.log(value)}
/>

How do I make value one of the values of options ('dark' | 'white') ? That is, I want to get this type:

type Value = 'dark' | 'white'

I must say right away this solution is not suitable:

const options = ['dark', 'white'] as const
type Value = typeof options[number]
Nikita
  • 39
  • 4

2 Answers2

7

Here is how to do it:

export interface SelectProps<O extends string> {
  value: O & {}
  options: O[]
  onChange: (value: O) => void
}

const Select = <O extends string>(props: SelectProps<O>) => <div></div>

As the others have said, SelectProps needs to be generic. We use O as an array for options and as the type for value. Important here are two things:

First, the intersection with an empty object {} for the value field. This signals to TypeScript that the value field shall not be used for inference of O.

Secondly, we constrain O to be a string. This lets TypeScript infer the string literal types 'dark' | 'white' instead of just string.

function main() {
    return (
        <div>
            <Select
                value="red" // Error: Type '"red"' is not assignable to type '"dark" | "white"'
                options={['dark', 'white']}
                onChange={(value) => console.log(value)}
            />
            <Select
                value="dark"
                options={['dark', 'white']}
                onChange={(value) => console.log(value)}
            />
        </div>
    )  
}

Playground

Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • 1
    Nice trick with `O & {}`! Learned smth new! I will include it in my blog, if you don't mind – captain-yossarian from Ukraine Oct 17 '22 at 07:48
  • 1
    @captain-yossarianfromUkraine nice to hear :D you can add it for sure - here are some sources: https://github.com/microsoft/TypeScript/issues/14829#issuecomment-320754731 & https://stackoverflow.com/a/73733110/8613630 – Tobias S. Oct 17 '22 at 07:52
  • @TobiasS. I would say the `& {}` reduces the priority of that site in inference. It will infer from it if there is nothing else to infer from. https://www.typescriptlang.org/play?#code/LAKAlgtgDg9gTgFwAQCUCmBDAxsgZnGCJAIjkx2NFDQA9ZEkwA7BNOXbNJAZTQBs0OAAoEoAZwA8AeSS1WTACZikYhHGYBzAHxIA3qCRIAbhj4BXNAC4kMgGR6AvgaQwoCMDCZiA-NakBtAF1nTwBhAAsMJg0rJAAKE3NYqQBKJABeHSMYMAVQJxBQLE9VHn5BZHSkaVkaeSUVNU0tOKhRMWteAWF26S00zOqFMCMtCQB6YdGqEFwzJhwPJiQIDGY4tP0QQ0MyBDM4ZbjnHZ2JKa0T0+uJLoqr68fjUwt00jQFYiRx8ZIyT4eT1OYUi0TQ6V0CReaAGOmKXhgAgAdHwYBooUkUgUgadxpdtk8JhcrmkkPlQEA – Titian Cernicova-Dragomir Oct 17 '22 at 09:09
  • 2
    @TobiasS. https://catchts.com/undocumented-features#inference – captain-yossarian from Ukraine Oct 22 '22 at 10:47
1

You need to use typescript Generics:

export interface SelectProps<Value> {
  value: Value
  options: Value[]
  onChange: (value: Value) => void
}

But you also need to change Select component, so it also takes a type parameter, and passes it to SelectProps interface:

    function Select<Value>(props:SelectProps<Value>){
                            ...
    }
Erfan
  • 1,725
  • 1
  • 4
  • 12
  • Hi, Erfan! But how do I create a literal type? Following your example, I will only get the string type! And I need the value to be a literal type, let's say ('1', '2', '3')! – Nikita Oct 17 '22 at 06:43
  • @Nikita From a personal experience: I have been where you are currently a lot writing react/typescript, I never found any solution other than creating a literal type of all possible values manually. – Erfan Oct 17 '22 at 06:50
  • 1
    thanks for the answer! Thanks to you, we managed to come up with such a solution! Perhaps it will be useful to you! - [codesandbox](https://codesandbox.io/s/mutable-morning-r32d3n?file=/src/Select.tsx) – Nikita Oct 17 '22 at 07:54