DEV Community

Ben Holmes
Ben Holmes

Posted on • Updated on • Originally published at bholmes.dev

Using Typescript string enums? Consider string literals!

If you've been using TypeScript for any amount of time, you've probably wondered this at least once:

Can I use string values instead of numbers on TypeScript enums?

This often comes up when you want a variable to have a select few string values. For example, say you're creating a banner for a website that's either yellow for warnings or red for emergencies. You want to make something reusable, so you add a enum for which type of banner it is:

enum BannerType = {
    Warning = "warning",
    Danger = "danger"
}
Enter fullscreen mode Exit fullscreen mode

This gives you a lot of flexibility of how you can use that enum's value. A common use might be defining a class name for styling your banner:

{/* Yes, this is written a JSX-y fashion for you React users */}
<div className={BannerType.Danger}>Uh oh!</div>
Enter fullscreen mode Exit fullscreen mode

This is much easier than writing weird helper functions and ternaries to figure out what class name to use. There are many more use cases for enum string values, like object keys, CMS content identifiers, paragraph text, error logs, etc etc etc.

When string enums fall flat

There's a few annoyances you might find with enums + string initializers:

  • They're a little verbose
  • They require lookups and tooltips to see what the actual string value is
  • They're limited in the special characters the enum can use

This last point was a huge point of friction for my web development team. To explain, we were looking to generate keys for content coming from the Contentful CMS. In Contentful, a key can be any string you could dream up. This means you can, say, include dots to indicate a subcategory (ex. "labels.danger") or dashes to mirror URL slugs (ex. "checkout-promo-code").

Clarification: A "CMS" is an external service to host all of the content for your website. In our case, we are using Contentful to store all of the header text, body text, images, and videos we display. In order to retrieve this content, we make an API call to fetch by specific keys.

This poses a problem for our enum solution. We need to use the keys in order to retrieve the site's content, and mapping each Contentful key to an enum means tossing out all the dots and dashes! Needless to say, this could lead to some nasty collisions between keys that are unique in Contentful but not unique on our hacky enums.

String literals to the rescue!

Luckily, TypeScript has a cleaner solution when you need those string values. You can provide a finite list of strings a variable can be assigned. Otherwise, it should throw a type error.

Example of assigning something invalid to a string literal type

This will also prevent you from assigning a traditional "string" type to the string literal. So, when declaring your types, you'll need to export the string literal type and use it the same way you would use an enum.

Example of autocomplete for string literals using VS Code

You can see from the informative gif above that autocomplete works as well!

Limitations

String literals aren't the silver bullet for every situation. Notably, using string literals doesn't improve on the verbose nature of enums. In fact, it'll often provide more information than necessary when assigning the literal type.

It's also more visually unclear what all possible values are when assigning 'random string' instead of SpecificTypes.Enum. This requires team communication to decide if string literals work best for smooth PR reviewing and text editor / IDE support.

Learn a little something?

Noice. In case you missed it, I launched an my "web wizardry" newsletter to explore more knowledge nuggets like this!

This thing tackles the "first principles" of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go beyond the framework, this one's for you dear web sorcerer ๐Ÿ”ฎ

Subscribe away right here. I promise to always teach and never spam โค๏ธ

Oldest comments (6)

Collapse
 
juliang profile image
Julian Garamendy • Edited

I would add that a benefit of string literal types is that they don't emit anything.

As opposed to a simple enum like this:

enum BannerType {
    Warning,
    Danger
}
Enter fullscreen mode Exit fullscreen mode

resulting in this:

var BannerType;
(function (BannerType) {
    BannerType[BannerType["Warning"] = 0] = "Warning";
    BannerType[BannerType["Danger"] = 1] = "Danger";
})(BannerType || (BannerType = {}));
Enter fullscreen mode Exit fullscreen mode

I like TypeScript when it's just type annotations, so I don't need to guess what's happening at runtime.

Here's an example in the TS Playground

Collapse
 
michaeljota profile image
Michael De Abreu

You can use const enum if you don't like that behavior.

Collapse
 
juliang profile image
Julian Garamendy • Edited

Yes, but I don't think they work when using the --isolatedModules compiler option.

I mean they work but only within a module. You can't export a const enum.

Thread Thread
 
michaeljota profile image
Michael De Abreu

If you have types imports and exports, most of the type checking won't work when you use isolated modules.

Collapse
 
thomvaill profile image
Thomas Vaillant

Unfortunately const enums are not supported anymore by babel :(
github.com/babel/babel/issues/8741

Thread Thread
 
kashif_shamaz profile image
Kashif Shamaz

Support for const enums is now available in @babel-preset-typescript from v7.15 using the optimizeConstEnums preset option

More info: babeljs.io/docs/en/babel-preset-ty...