I have an enum
in TypeScript.
I want to write a function that returns an exhaustive array that contains exactly one reference to each property on the enum.
The return of the function will be an Array<{ id: EnumValue, label: string }>
.
Sample code:
// I have this enum:
enum StepId {
STEP_ONE = 'step-one-id',
STEP_TWO = 'step-two-id',
}
// I want to define some function...
function someFunc() {
return ...
}
// which returns an Array where every value from `StepId` is represented as an object with that value as the `id` field.
someFunc() === [
{ id: CustomStepIds.STEP_ONE, label: 'Step One', },
{ id: CustomStepIds.STEP_TWO, label: 'Step Two', },
];
I have tried to first solve the problem of enforcing all enum values are represented in an array. I've tried this:
enum CustomStepId {
STEP_ONE = 'step-one-id',
STEP_TWO = 'step-two-id',
}
type ValueOf<T> = T[keyof T];
const stepConfig: Array<{ id: ValueOf<typeof CustomStepId>, label: string }> = [
{ id: CustomStepId.STEP_ONE, label: 'Step One', },
{ id: CustomStepId.STEP_TWO, label: 'Step Two', },
{ id: CustomStepId.STEP_TWO, label: 'Step Two', }, // this should error, as STEP_TWO is already defined
];
However as noted in the comment above, while this enforces id
is a CustomStepId
, it does not enforce uniqueness of the field.
I propose the following, on the basis that I (and much of the Typescript world) eschew Enums in favour of const arrays See typescript playground
I drafted mechanisms based on a map or a tuple, depending what you prefer or need.
type MemberOf<Arr extends readonly unknown[]> = Arr[number]
const STEP_IDS = ['step-one-id', 'step-two-id'] as const;
type StepId = MemberOf<typeof STEP_IDS>;
type StepConfigMap = {[k in StepId]: {
label:string
}}
type ConfigTuple<Tuple extends readonly [...unknown[]]> = {
[Index in keyof Tuple]: {
id:Tuple[Index],
label:string
};
} & {length: Tuple['length']};
type StepConfigTuple = ConfigTuple<typeof STEP_IDS>;
const CONFIG_MAP: StepConfigMap = {
"step-one-id":{
label:"something"
},
"step-two-id":{
label:"something else"
},
// errors as it's already defined
// An object literal cannot have multiple properties with the same name.(1117)
"step-two-id":{
label:"something else"
}
} as const;
const CONFIG_TUPLE = [
{
id:"step-one-id",
label:"something",
},
{
id:"step-two-id",
label:"something",
},
// errors as it's already defined
// Source has 3 element(s) but target allows only 2.
{
id:"step-two-id",
label:"something",
}
] as const satisfies StepConfigTuple;