BITS

Introduction

Javascript is a pretty popular language, and a lot of that popularity goes to it's un-opinionated nature. There is no strict way of doing stuff. That is why there are so many best practices for javascript (and it's huge number of frameworks). In that pile of "best" practices, one is likely to fall into a trap!

The trap is not that a programmer is going to adopt a set of bad practices – which also happens – but that one is going to adopt it without even thinking about it. It is so common in programming that it has a name, Cargo cult programming. Explanation of cargo cult programming from Wikipedia:

"Cargo cult programming can also refer to the results of applying a design pattern or coding style blindly without understanding the reasons behind that design principle."

So in this series of bits, "Don't overcomplicate Javascript", I plan to talk about my observations of such trends, tools and code written by myself or other people. You may or may not agree with me in every occurrence, but I strongly encourage you to start reasoning about what you use as a programmer.

Disclaimer: It's called "Don't overcomplicate Javascript" and not "Don't overcomplicate programming" because I will be strongly scoped to Javascript and it's huge ecosystem.

Observation #1 – Redux constants

If you are using Redux, or at least introduced to it, it is likely you have seen something like this:

// constants1.js

export const LOAD_EVENTS_REQUEST = 'LOAD_EVENTS_REQUEST'
export const LOAD_EVENTS_SUCCESS = 'LOAD_EVENTS_SUCCESS'
export const LOAD_EVENTS_FAILURE = 'LOAD_EVENTS_FAILURE'

export const ADD_EVENT_REQUEST = 'ADD_EVENT_REQUEST'
export const ADD_EVENT_SUCCESS = 'ADD_EVENT_SUCCESS'
export const ADD_EVENT_FAILURE = 'ADD_EVENT_FAILURE'

export const DELETE_EVENT_REQUEST = 'DELETE_EVENT_REQUEST'
export const DELETE_EVENT_SUCCESS = 'DELETE_EVENT_SUCCESS'
export const DELETE_EVENT_FAILURE = 'DELETE_EVENT_FAILURE'

export const EDIT_EVENT_REQUEST = 'EDIT_EVENT_REQUEST'
export const EDIT_EVENT_SUCCESS = 'EDIT_EVENT_SUCCESS'
export const EDIT_EVENT_FAILURE = 'EDIT_EVENT_FAILURE'

It looks silly, so let's explain:

Basically in redux you have actions that you can dispatch (call), and you respond to those actions to do some other stuff. A man's gotta name his actions in order to identify them. An action name is simply a string. A reducer responds to actions thus needs to know action names.

The premise goes like this: "It is better to assign names to constants and import them in action creators and reducers." So instead of this:

// actions0.js

export const loadEventsRequest = (x) => {
    return {type: 'LOAD_EVENTS_REQUEST', x}
}

// reducer0.js

export default function reducer(state, action) {
    switch(action.type) {
        case 'LOAD_EVENTS_REQUEST': {
            // ...
        }
        // ...
    }
}

You would have this:

// constants1.js

export const LOAD_EVENTS_REQUEST = 'LOAD_EVENTS_REQUEST'

// actions1.js

import { LOAD_EVENTS_REQUEST } from './constants1.js'

export const loadEventsRequest = (x) => {
    return {type: LOAD_EVENTS_REQUEST, x}
}

// reducer1.js

import { LOAD_EVENTS_REQUEST } from './constants1.js'

export default function reducer(state, action) {
    switch(action.type) {
        case LOAD_EVENTS_REQUEST: {
            // ...
        }
        // ...
    }
}

It's such a common practice that some people actually take it as mandatory in redux. It comes with most (react-)redux boilerplates. I was very curious about this, so after a research the following arguments for using redux consants came up:

  1. It allows you to easily find all usages of that constant across the project (if you use an IDE).
  2. If you want to change an action's name you do it in one place.
  3. It prevents you from introducing bugs caused by typos. You will get a ReferenceError immediately.

So let's respond to those arguments.

It allows you to easily find all usages of that constant across the project (if you use an IDE).

This can also be acomplished by a global search of the action string.

If you want to change an action's name you do it in one place.

Doesn't really occur to me as a use case. Different action and constant name would only cause confusion. (And then there's also global search and replace).

It prevents you from introducing bugs caused by typos. You will get a ReferenceError immediately.

A while ago when I started with redux this seemed like a useful thing to have. Do not get me wrong, it is – but this usually never happens. I work with react+redux apps with 150+ action types and it never did happen. If this is benefitial enought for you, there is a big readability price to pay. You have to import every single action constant, and that gets pretty messy with larger codebases.
You can not import * as constants from './constants' because then, if you make a typo, you won't get a ReferenceError, the value will just be undefined. Instead, you must do this:

// reducer2.js
import {
    LOAD_EVENTS_REQUEST,
    LOAD_EVENTS_SUCCESS,
    LOAD_EVENTS_FAILURE,

    ADD_EVENT_REQUEST,
    ADD_EVENT_SUCCESS,
    ADD_EVENT_FAILURE,

    DELETE_EVENT_REQUEST,
    DELETE_EVENT_SUCCESS,
    DELETE_EVENT_FAILURE,

    EDIT_EVENT_REQUEST,
    EDIT_EVENT_SUCCESS,
    EDIT_EVENT_FAILURE,
} from './constants'

export default function reducer(state, action) {
    switch(action.type) {
        case LOAD_EVENTS_REQUEST: {
            // ...
        }
        // ...
    }
}

Packages that "make your life easier"

The good people of js community have created a lot of packages that deal with this kind of stuff, but all with aforementioned premise. Here are some of them: key-mirror, redux-define, keykey, react-constant — all of which I've seen in a lot of redux boilerplates and projects.

But ask yourself, is this valuable enough? After all, it is one more dependency/tool in your project. You and your coworkers have to learn it, and the project depends on it. I am pretty sure these libraries are useful to a lot of people, however, ask yourself are you one of them.
Is this a fair trade-off?

Conclusion

Just to be clear, I know there are benefits of using action constants. However if you don't pay attetion, you may bring complexity and tech debt to the project.

The purpose of this series is not to point out bad practices, but to encourage you to reason about the software and tools you use on a daily basis. It is so easy to think that a practice/package/tool would be such an improvement, when it's not.

More often than not, the best solution is the simplest one.