DEV Community

Cover image for TypeScript's enum + JavaScript's "in" = πŸ‘
Andy Coupe
Andy Coupe

Posted on

TypeScript's enum + JavaScript's "in" = πŸ‘

At cinch, I work on the inventory team and we manage all of the vehicle inventory, tracking the status, location and all kinds of other data for thousands of cars. We have an event driven architecture which means parts of our code respond to certain events which we subscribe to. We have a state machine/model which contains a LOT of logic to determine moving cars from one status to another. I came up with the following pattern when I noticed some hefty if conditions in the codebase.

In this example I'm going to use robots as the subject as I write code to do with vehicles every day! πŸš—

Let's imagine we want a function which disposes of a robot but we can only dispose of the robot if it's status is faulty or damaged. Below we setup our enum RobotStatus to list our possible robot status and a Robot interface to build the shape of our robot.

enum RobotStatus {
    ready,
    damaged,
    faulty
}

interface Robot {
    name: string
    status: RobotStatus
}

function disposeRobot(robot: Robot): void {
    if (robot.status === RobotStatus.damaged || 
        robot.status === RobotStatus.faulty
    ) {
        console.log('Disposing of robot...')
    }

    console.log('Robot in incorrect state to dispose...')
}
Enter fullscreen mode Exit fullscreen mode

This is fine for now. But imagine if we had to start adding more checks for other status. Let's add some more for discontinued, dead, old, rusty, and dangerous.

enum RobotStatus {
    ready,
    damaged,
    faulty,
    discontinued,
    dead,
    old,
    rusty,
    dangerous
}

interface Robot {
    name: string
    status: RobotStatus
}

function disposeRobot(robot: Robot): void {
    if (robot.status === RobotStatus.damaged || 
        robot.status === RobotStatus.faulty ||
        robot.status === RobotStatus.discontinued ||
        robot.status === RobotStatus.dead ||
        robot.status === RobotStatus.old ||
        robot.status === RobotStatus.rusty ||
        robot.status === RobotStatus.dangerous ||
    ) {
        console.log('Disposing of robot...')
    }

    console.log('Robot in incorrect state to dispose...')
}
Enter fullscreen mode Exit fullscreen mode

Now that if block is getting chunky and it stinks πŸ‘ƒ.
Let's create an enum containing our allowed disposable statuses.

enum DisposableStatus {
    damaged,
    faulty,
    discontinued,
    dead,
    old,
    rusty,
    dangerous
}
Enter fullscreen mode Exit fullscreen mode

JavaScript has an in operator which will return true if the specified property exists in the specified object.

prop in object
Enter fullscreen mode Exit fullscreen mode

This can clean up our if block from above. Let's use it...

function disposeRobot(robot: Robot): void {
    if (robot.status in DisposableStatus) {
        console.log('Disposing of robot...')
    }

    console.log('Robot in incorrect state to dispose...')
}
Enter fullscreen mode Exit fullscreen mode

TypeScript will compile the enum into a regular JavaScript object and these properties will exist at runtime πŸ‘.
This is much more readable and easier to digest for your future self and other developers.

Top comments (7)

Collapse
 
0916dhkim profile image
Danny Kim

Does this mean RobotStatus.damaged === DisposableStatus.damaged ? It is a cool mechanism, but also confusing to me. I prefer using string unions over enums, and one of the reasons is this comparison issue. To me, two enums should never be equal to each other like an int and a string cannot be equal (as in ===). JS is a mess because it allows too many non-trivial comparisons, and TS solves many of its problems. It feels weird to see this type of magic working. I would do something like the following instead:

type RobotStatus = 'ready' | 'damaged' | 'faulty';
const disposableStatus: Set<RobotStatus> = new Set(['damaged', 'faulty']) as const;

function disposeRobot(robot: Robot): void {
  if (disposableStatus.has(robot.status)) {
    // Dispose.
  }
  // Invalid state.
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
indoor_keith profile image
Keith Charles

Sounds like a finite state machine to me! Something that web dev has yet to really embrace fully. This is a great example of how powerful FSMs are and why we should be using them a lot more often with our development. Great post! πŸ‘

If you or anyone is interested in learning more about FSMs, React Podcast had a great episode on the very topic which you can find here: reactpodcast.com/episodes/5

Collapse
 
adam_cyclones profile image
Adam Crockett πŸŒ€

Agreed, I'm an avid xState fan

Collapse
 
ignusg profile image
Jonathan • Edited

Don't do this. It produces wrong results since it relies on the enum's internal value which is just a number - it matches wrong enum values to each other:

enum Type {
Disabled, // This is 1
Enabled, // This is 2
Pending, // This is 3
}

enum AllowedTypes {
Enabled, // This is 1
Pending, // This is 2
}

const state = Type.Enabled;

console.log(Type.Pending in AllowedTypes); // This checks if 3 is in { 1: 'Enabled', 2: 'Pending' } which is why it returns false

PS: You "could" technically make this work with string enums but you would have to make sure the value you assign matches the key and there are no duplicates (this is not checked by TS)

Collapse
 
andrewmcoupe profile image
Andy Coupe • Edited

Ah yes, good spot! I had a check and the status is received as a string value from our database so in my case it works well.

Collapse
 
adam_cyclones profile image
Adam Crockett πŸŒ€

I think Enum results in an object so there isn't any magic going on here, just normal JavaScript. A quick note on in, it checks prototype properties as well which in the world of useless micro optimization this would be a little slower.

Collapse
 
coding_tom profile image
Tom Cafferkey

Thanks Andrew.