1

I'm building a Dropdown component in React. It takes in an options object and optionally a value which will preselect one of the given options. I want to be able to type value so that it must be one of the names given in options.

An individual option looks like this:

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

And the props for the component look like this:

interface Props {
    options: Option[];
    value?: ValidOption; // How can I write ValidOption?
}

Is it possible to make it so that value must be one of the values from the array of options?

For example, given this array of options:

const options = [
    { name: 'Option1', value: 'option1' },
    { name: 'Option2', value: 'option2' },
]

I would like ValidOption to equal this:

type ValidOption = 'option1' | 'option2';
Brian Schlenker
  • 4,966
  • 6
  • 31
  • 44

1 Answers1

1

Your question is similar to this question. There's no single type Props type you can define that accepts exactly objects of the form {options, value} where the value is one of the options. You could start by introducing a type parameter O for the valid options:

interface Option<O extends string> { 
    name: string;
    value: O;
}
interface Props<O extends string> {
    options: Option<O>[];
    value?: O;
}

but then you run into two problems:

  1. You can't write a single type that means "Props<O> for some O". This would be an existential type, which TypeScript currently doesn't support. Props<any> doesn't work: it places no restriction on the options and the values.
  2. If a caller manually specifies O, there's no way to enforce that options contains all the values of O.

In light of these problems, the closest you can come is to write a generic function that you can call with an {options, value} object literal, such that type inference will succeed if the value is one of the options and fail if it isn't. This may or may not be useful depending on the structure of the rest of your code.

// Building on `Option` and `Props` defined above.

// Needed to prevent widening when storing the options array in a local
// variable.  If the options array is passed directly to `checkProps`,
// this isn't needed.
function asOptions<O extends string>(options: Option<O>[]) { 
    return options;
}

const options = asOptions([
    { name: 'Option1', value: 'option1' },
    { name: 'Option2', value: 'option2' },
]);

// We need to use two type parameters to detect errors.  If we use just
// `O`, then TypeScript will try to be helpful and infer `O` to be the
// union of the options and the value. 
function checkProps<O extends string, O1 extends O>(
    props: { options: Option<O>[], value?: O1 }) { 
    return props;
}

let myProps1 = checkProps({
    options: options,
    value: "option1"  // OK
});

let myProps2 = checkProps({
    options: options,
    value: "option3"  // Error
});

let myProps3 = checkProps({
    options: [
        { name: 'Option1', value: 'option1' },
        { name: 'Option2', value: 'option2' },
    ],
    value: "option3"  // Error
});
Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75