Try to map through an enum in TS with Object.entries<T>(Enum).map(), but only works with string based enums?

Viewed 80

I'm building a react form where a user has to select some options for a job, where the options are each represented by enums. Given what I have to accomplish, some of these enums are based on strings, and others are number based.

Example, string based

export enum ProjectType {      
    Construction = 'Construction',        
    Drilling = 'Drilling',      
    Completions = 'Completions',     
    Workover = 'Workover / Abandonment',       
    Water = 'Water Sourcing',       
    FracSatellite = 'Satellite Frac Monitoring',
    FracDesign = 'Frac Engineering & Design'
}

and then number based

export enum ProjectStatus {
    AllProjects = 0,
    Active = 1,
    Operational = 2,
    Pending = 3,
    Closed = 4,
}

for the string based enums, I can create a select field easily with the Object.entries<T>().map() like so.

<SelectField>
    {Object.entries<ProjectType>(ProjectType).map(([en, type]) => {
           return (<option value={en}>{type}</option>)                                            
    })} </SelectField>

But, if I'm doing this on a number based enum, Object.entries<T>(enum).map() gives me ALL the entries for the enum, both the string and the related number, so the select field would show

0
1
2
3
4
All Projects
Active
Operational
Pending
Closed

As a result, I have to make this really ugly filter etc to get the desired result, as shown below

 <SelectField>
      {Object.values(ProjectStatus)
         .filter((v) => !isNaN(parseInt(v as string)))
         .filter(v => parseInt(v as string) !== ProjectStatus.AllProjects)
         .map(x => {
             return (<option value={x}>{ProjectStatus[parseInt(x as string)]}</option>)
       })} </SelectField>

Is there any way to do this? I just feel that this solution that I am using now to get the desired result is pretty clanky and likely there is a better solution.

Thanks!

1 Answers

By design, enums have a reverse mapping (unless you initialize them with a string, as you noted). If this is undesirable, you should use a const object instead:

const ProjectStatus = {
    AllProjects: 0,
    Active: 1,
    Operational: 2,
    Pending: 3,
    Closed: 4,
} as const; // Make all properties readonly
ProtjectStatus.Active = 2
//             ^^^^^^ Error: Cannot assign to 'Active' because it is a read-only property.

For most use cases, this is effectively the same as an enum, sans the reverse mapping (and slightly less generated code).

If you absolutely need an enum, you are stuck filtering. You might be able to improve with a helper function:

function getEnumEntries<T extends {}>(e: T) {
    return Object.entries(e)
        .filter(([key, _]) => Number.isNaN(key as any - 0))
}
Related