Enumeration Objects in JavaScript
📢 Stop repeatedly typing the same String
everywhere! 😆
Ever got tired of writing the same string over and over? Had a case where your code wasn’t working only to find later that it’s because you misspelled a class name by one character? 🤦 Some form of enumeration would likely help.
Disclaimer, we aren’t referring to strict enumeration seen in strongly typed languages. This isn’t strictly possible with JavaScript
. We look at the next best thing we have in JavaScript
without jumping into TypeScript
. If you’ve used Redux
or Flux
you’ve likely come across this form when defining Action
types for your project.
For those in camp TL;DR, create Object
s with reference to commonly used strings like class names or action types and freeze them. Use the references instead of repeating String
across your code and reduce the risk of developer error 💪
What’s enumeration? permalink
Enumeration(enumerated types) allows you to define constants for use within your code.
In computer programming, an enumerated type (also called enumeration, enum, or factor in the R programming language, and a categorical variable in statistics) is a data type consisting of a set of named values called elements, members, enumeral, or enumerators of the type. The enumerator names are usually identifiers that behave as constants in the language. An enumerated type can be seen as a degenerate tagged union of unit type. A variable that has been declared as having an enumerated type can be assigned any of the enumerators as a value. In other words, an enumerated type has values that are different from each other, and that can be compared and assigned, but are not specified by the programmer as having any particular concrete representation in the computer’s memory; compilers and interpreters can represent them arbitrarily.
For example, the four suits in a deck of playing cards may be four enumerators named Club, Diamond, Heart, and Spade, belonging to an enumerated type named suit. If a variable V is declared having suit as its data type, one can assign any of those four values to it. — Wikipedia, Enumerated Type
Quite a lot to process there. Got it? Great! 😃
Why use enumeration in JavaScript? permalink
One of the main reasons to use enumeration in JavaScript is to reduce the risk of developer error. We are not all perfect! 😜
How? permalink
We can reduce the risk of developer error by aiming not to repeat String
variables in code. A common use case in front end development would be if we needed to apply and remove class names on elements. Assume, we use the class once to query for an element. Then again for making changes to the class name. And then again later to revert those changes. That’s three instances we have typed out one class name. In this case, would it not be better to store this class name somewhere and reference it? The benefit of doing so is that if we want to change the class name later, we need only do it in one place 👍
Also, before making the reference, if we had misspelled the class name, the code would not care. If we misspell a reference to a class name, our code will throw an error ✋
This can be particularly useful if our class names become quite long. This form of enumeration is often seen in projects using react-redux
or similar, to define action types ⚛️
In the second case we can define a finite set of allowed values for a variable. Consider an example where a user is selecting an animal. In our code we define a finite list of animals, let’s say cat, dog, and mouse 🐭 The user types rabbit. We can check against our enumeration object to see if rabbit
is one of the possible values. We would find that it is not and be able to prompt the user to retype.
The basic code permalink
If you hadn’t guessed yet, we are going to use Object
s for our enumeration. It’s the best fit for what we desire. We can store multiple keys for values and use them as a reference 👌
Let’s crack on and consider use case one, keeping a reference to class names within our code. Let’s assume we have some click event handler that adds a class and on clicking away removes the class.
const element = document.querySelector('.some-real-long-class')
const showElement = (e) => {
const processClick = (evt) => {
if (e !== evt) {
element.classList.remove('some-real-long-class--visible')
element.IS_SHOWING = false
document.removeEventListener('click', processClick)
}
}
if (!element.IS_SHOWING) {
element.IS_SHOWING = true
element.classList.add('some-real-long-class--visible')
document.addEventListener('click', processClick)
}
}
This is quite a small example but we can already see the potential for error here. What if we start using this class in various places and that one time we write, some-real-long-calls--visible
? Our app would break but there would be no apparent error thrown 👎
Let’s rewrite that code extracting the class names. Consider;
const CLASSES = {
ELEMENT: 'some-real-long-class',
VISIBLE: 'some-real-long-class--visible',
}
const element = document.querySelector(`.${CLASSES.ELEMENT}`)
const showElement = (e) => {
const processClick = (evt) => {
if (e !== evt) {
element.classList.remove(CLASSES.VISIBLE)
element.IS_SHOWING = false
document.removeEventListener('click', processClick)
}
}
if (!element.IS_SHOWING) {
element.IS_SHOWING = true
element.classList.add(CLASSES.VISIBLE)
document.addEventListener('click', processClick)
}
}
That’s already a great improvement. If we mistype the class reference, we will get an Error
. There is less risk of us making an error now when using the String
.
There is no strict way to enumerate the values, it’s whatever feels best 👍 There is no right or wrong. You could write the above alternatively as
const CLASSES = {
ELEMENT: 'some-real-long-class',
VISIBLE_MODIFIER: 'visible',
}
const element = document.querySelector(`.${CLASSES.ELEMENT}`)
const showElement = (e) => {
const processClick = (evt) => {
if (e !== evt) {
element.classList.remove(`${CLASSES.ELEMENT}--${CLASSES.VISIBLE_MODIFIER}`)
element.IS_SHOWING = false
document.removeEventListener('click', processClick)
}
}
if (!element.IS_SHOWING) {
element.IS_SHOWING = true
element.classList.add(`${CLASSES.ELEMENT}--${CLASSES.VISIBLE_MODIFIER}`)
document.addEventListener('click', processClick)
}
}
It still works! 🎉
Maybe a little verbose in comparison but it’s whatever feels best.
Freeze it! permalink
Last but definitely not least, we need to freeze our enumerations. We can do this using Object.freeze
. This makes our enumeration Object
s immutable. This means that we can’t add or remove properties. Nor can we update existing values for properties. This is very important if we want our enumeration to be effective 🍨
const ANIMALS = {
cat: 'cat',
dog: 'dog',
mouse: 'mouse'
}
Object.freeze(ANIMALS)
ANIMALS.rabbit = null // Error, Object is not extensible
ANIMALS.cat = 'dog' // Error, cannot assign to read-only property
delete ANIMALS.mouse // Error, cannot delete property
Take it a step further permalink
So that’s all good but I don’t want to be typing hasOwnProperty
checks and freezing things all the time. So we could package up our enumeration into something reusable 👍
class Enumeration {
constructor(obj) {
for (const key in obj) {
this[key] = obj[key]
}
return Object.freeze(this)
}
has = (key) => {
return this.hasOwnProperty(key)
}
}
This is a foundation for what we want. A standout flaw in this implementation though would be if we wanted to set an enumeration value that already existed on the class such as has
. Also, what if we want an object with keys that match the values? Such as;
const enums = { A: "A", B: "B" }
This is a use case seen often in apps using redux
. It’s not a great experience to have to write all that out.
That won’t do 👎 Our solution is trying to do too much. It’s better to create two solutions that do what we want well 👍
If we want a class reference, create a frozen object ❄️
const CLASSES = Object.freeze({
VISIBLE: 'is-visible',
HIDDEN: 'is-hidden'
})
CLASSES.HIDDEN // is-hidden
If you need something along the lines of a key mirror, use this one-liner function with an Array 👍
const mirror = Object.freeze(values.reduce((keys, key) => ({ ...keys, [key]: key }), {}))
const ACTIONS = mirror(['SEND', 'UPDATE', 'ERROR'])
ACTIONS = {SEND: 'SEND', UPDATE: 'UPDATE', ERROR: 'ERROR'}
I’ve also packaged the latter up into a tiny node module you can import if you like 👍 Check out key-book here.
It also offers prefix/suffix support so you could create an action map for your React app like this
import book from 'key-book'
const actions = book(['SEND', 'DELETE', 'ARCHIVE'], 'MESSAGES__')
actions // {MESSAGES__SEND: "MESSAGES__SEND", MESSAGES__DELETE: "MESSAGES__DELETE", MESSAGES__ARCHIVE: "MESSAGES__ARCHIVE"}
That’s it! permalink
Using enumeration objects in JavaScript is a neat and concise solution. It can be pretty effective at mitigating developer errors when used in your code 💪