DEV Community

Cover image for Don't use Enums in Typescript, they are very dangerous 😨

Don't use Enums in Typescript, they are very dangerous 😨

Ivan Zaldivar on January 20, 2023

Have you ever wondered why TypeScript experts recommend avoiding the use of ENUMs? While they may seem like a useful tool for defining a set of con...
Collapse
 
trueromanus profile image
Roman Vladimirov

ENUMs generate additional code at compile time, which increases the size of the final file.

This is a rather strange statement considering that typescript always generates more code than you write.

Collapse
 
speederx profile image
SpeederX

I think he implied: compared to using const.

Collapse
 
trueromanus profile image
Roman Vladimirov

Ok let check it. I have this code:

enum Roles {
  Opt1 = 'myconstantttestoption1',
  Opt2 = 'myconstantconsttestoption2',
  Opt3 = 'myconstantconsttestoption3'
}

const enum ConstRoles {
  TestOption1 = 'myconstantconttestoption1',
  TestOption2 = 'myconstantconsttestoption2',
  TestOption3 = 'myconstantconsttestoption3'
}

let test1:ConstRoles = ConstRoles.TestOption1 
//... another 11 lines using ConstRoles.TestOption1 
let consttest1:Roles = Roles.Opt1
//... another 11 lines using Roles.Opt1
Enter fullscreen mode Exit fullscreen mode

What we have:
ConstRoles used 12 times and have 324 characters (27 * 12)
Roles used also 12 times and have 306 (10 * 12 = 120, 120 + 186 enum definition character = 306).
Seems const enum occupied more characters then enum in this case. Ok may be my example too rude but it reflect my opinion about talks like "for this will be generate more code then for another one" can't will be common it always local and dependece from use case.

Thread Thread
 
speederx profile image
SpeederX

The article talks compares

Enum and Const

not

Enum and Const enum

Also it talks about

ENUMs generate additional code at compile time, which increases the size of the final file.

We're not talking about typescript code. We're talking about javascript converted code from typescript. Const has a native type in javascript whilst enum doesn't, so it has to be converted.

Take a look at this (which is the example above in the article):
Playground Link

Even if you copy and paste your code into that playground you can see enum differences with const, also that const enum should be avoid, it's glitchy sometime and bundle size wise it's better to use const. In some case it's just better to use Type, rather than also const itself. Depends on what you're doing, but enum should be avoided.

Thread Thread
 
trueromanus profile image
Roman Vladimirov

"We're not talking about typescript code." - I'm also talking about JavaScript and all conslusions in my previous comment also about JavaScript not TypeScript.

"Even if you copy and paste your code into that playground you can see enum differences with const" - ok let's compare it:

var Roles;
(function (Roles) {
    Roles["Admin"] = "admin";
    Roles["Writer"] = "writer";
    Roles["Reader"] = "reader";
})(Roles || (Roles = {}));
Enter fullscreen mode Exit fullscreen mode

and

const Roles1 = {
    Admin: "admin",
    Writer: "writer",
    Reader: "reader"
};
Enter fullscreen mode Exit fullscreen mode

I see only one difference Roles defined with var, Roles1 defined as const. Constant on Roles1 not guarantee anything actually. I can to do in console Roles1.Admin = "writer" and yes it will be works. Correct way to do constant is make Object.freeze(Roles1); after definition of object. Another way is const RolesAdmin = "admin"; const RolesWriter = "writer";....

"it's glitchy sometime" may be don't use TypeScript at all if it have glitches? :)

Collapse
 
bcostaaa01 profile image
Bruno

Good article!👏

You could also use enums in combination with interfaces, types of classes to encapsulate the behaviour of the enum, but, to be honest, I don’t really see a usage of an enum. You have far better solutions which are type safer such as string literals:

type Roles = 'admin' | 'writer' | 'reader';
Enter fullscreen mode Exit fullscreen mode

This adds the benefit of preventing typos because that allow you to match the exact string value that is desired to be used. You have other better solutions as well, but this one should be a good one to replace the usage of an enum.

Collapse
 
miss_developer profile image
Farzaneh

sure, but exept when your strings were too lang.

Collapse
 
lexlohr profile image
Alex Lohr

I prefer unions. More portable, you still get auto-complete and they are compiled away from the code into types only, without ever reaching the JavaScript output.

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen

They also support codebase-wide renaming for most but a few edge cases.

Collapse
 
bwmirek profile image
Ngh7

type RoleKeys = typeof Roles[keyof typeof Roles]

This line basically generates union from Roles object, still giving you access to "enums-like" syntax: Roles.Admin

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
lexlohr profile image
Alex Lohr

Union types, a.k.a. unions, are types, too. You know, like

enum ToggleState {
  Indeterminate = "indeterminate",
  On = "on",
  Off =" off",
}
// vs.
type ToggleState = "indeterminate" | "on" | "off" // <- this is a union
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
spock123 profile image
Lars Rye Jeppesen

Yes of course they are also types.

Collapse
 
amanecer2 profile image
amanecer • Edited

Great article!
If you write export const enum you won't generate single value.
And it will compile to nothing and in thee code it will have the hard code value

export const enum Enum {
FIRST = 'name'
}

Collapse
 
leandro_n_ortiz profile image
Leandro Ortiz • Edited

I like the string Enum approach and I haven't face those issues when trying to use the real string instead of the enum key.

If you need to explicitly pass the string, you can use "as" to force its mapping. It will map perfectly with the string value of your enum.
I know we should avoid using "as", but it's a very unlikely situation (I can't think in a use case to explicitly pass a string if I have the enum keys).

I think the most common use case will be to match data received from the backend (string) with the frontend enum type. But just using "as" will map perfectly.

Based on the example in the article:

// frontend enum (the string value is the same used by the backend)
enum Roles {
  Admin = 'admin',
  Writer = 'writer',
  Reader = 'reader'
}

// frontend type definition
type User {
  id: string;
  name: string;
  role: Roles;  // defined using the frontend enum
}

// So, for a function with this enum as argument
declare function hasAccess(role: Roles): void;

hasAccess('admin') // Invalid
hasAccess('admin' as Roles) // Valid  (but very unlike to happen)
hasAccess(Roles.Admin) // Valid

// For a backend response mapping:
const userDto = await getUser();

const myUser: User = {
  id: userDto.id,
  name: userDto.name,
  role: userDto.role as Roles,  // the most common case
}
Enter fullscreen mode Exit fullscreen mode

And by using enums instead of "objects as const", I don't need to do things like RoleKeys = typeof Roles[keyof typeof Roles] just to be able to use it.

Collapse
 
digioi profile image
Mike DiGioia

the down side to doing something like hasAccess("fish" as Roles) you are basically saying, trust me TS, I know this is right. which is the danger that the article is warning about.

Collapse
 
trueromanus profile image
Roman Vladimirov

As you can see enums are a named type and only accept enum-specific values, not compatible or similar values, which can lead to compatibility issues and should be carefully considered when designing and using enums.

Well, i.e. in the paragraph above, you write how bad it is that the typescript does not check that the value is in the range of the Enum. And here you write how to make it possible for strings, which will eventually lead to the same problems in the future as in the previous example.

In short, using objects or types instead of ENUMs is a better option in terms of flexibility, scalability, clarity, readability, and security.

May be you share examples in post?

Collapse
 
yarokon profile image
Yaroslav Shuhailo

Example from "Numeric types usafe" is no longer relevant since TypeScript 5.0

Collapse
 
brense profile image
Rense Bakker

Articles like these are great 👍 very good explanation of the problem and the solution! Many thumbsup 👍👍👍