Coding Conventions

May 4, 2017 ยท View on GitHub

Here you can read about our preferred ways of writing modern JavaScript.

Classes

Use ES2015 classes instead of prototype-based inheritance. Having a single declarative form makes class patterns easier to use, and encourages interoperability.

class Todo {
  constructor(text) {
    this.text = text
  }
}

Class properties

Use instance properties to define initial values for class properties instead of defining them inside the constructor. Constructors are more useful when the class takes its initial values from the outside.

Note: You need to include the transform-class-properties Babel plugin in order to use class properties.

Do

class Todo {
  text = 'Something to do'
}
class Todo {
  constructor(text) {
    this.text = text
  }
}

Do NOT do

class Todo {
  constructor() {
    this.text = 'Something to do'
  }
}

Static keyword

Use Static keyword instead of the traditional Todo.staticMethod syntax because it will allow you to contain all code related to particular class inside the class itself.

Do

class Todo {
  static label = 'Todo'

  static staticMethod() {
    console.log('Static method has been called')
  }

  static anotherStaticMethod() {
    Todo.staticMethod()
  }
}

Do NOT do

class Todo {}

Todo.label = 'Todo'

Todo.staticMethod = () => {
  console.log('Static method has been called')
}

Todo.anotherStaticMethod = () => {
  Todo.staticMethod()
}

Arrow functions

Use arrow functions instead of traditional functions because they share the same lexical this (or scope) with the surrounding code, which is desirable in most cases.

Class methods

Arrow functions are especially useful as class methods, because you do not need to bind every method to the scope separately.

Do

class Todos {
  addTodo = (todo) => {
    // ...
  }
}

Do NOT do

class Todos {
  constructor() {
    this.addTodo = this.addTodo.bind(this);
  }

  addTodo(todo) {
    // ...
  }
}

Function expressions

Arrow functions (or function expressions) are more convenient than traditional functions, especially when currying, because you do not need to add a return statement for each nested function and you can even omit the curly brackets for one-line functions.

Note: Functions that are defined using the function keyword (also known as named functions) are hoisted to the top of the scope, which means that you can invoke them even before they are defined. This can promote bad design and cause confusion.

Do

const addTodo = () => {
  // ...
}

Do NOT do

function addTodo() {
  // ...
}

Resources

Async functions

Use async/await instead of Promise.prototype.then.

Do

try {
  const response = await fetch('https://example.com')
  const body = await response.json()
  console.log(body)
} catch (error) {
  console.error(error)
}

Do NOT do

fetch('https://example.com')
  .then((response) => response.json())
  .then((body) => {
    console.log(body)
  })
  .catch((error) => {
    console.error(error)
  })

Use Promise.all when running multiple async functions in parallel.

Do

const [orders, customers] = await Promise.all([
  getOrders(),
  getCustomers(),
])
console.log(orders, customers)

Do NOT do

Promise.all([
  getOrders(),
  getCustomers(),
]).then(([orders, customers]) => {
  console.log(orders, customers)
})

Template literals

Use template literals instead of string concatenation. Template literals allow for embedded expressions and you can use them even with multi-line strings.

Do

const hello = `Hello ${name}!
This syntax is so convenient.`

Do NOT do

const hello = 'Hello ' + name + '!' +
'This syntax is not so convenient.'

Destructing assignment

Use destructing assignment wherever possible. Destructing allows for a shorter syntax that is easier to read.

Do

const Todos = ({todos, onTodoClick}) =>
  <ul>
    {todos.map((todo) =>
      <li><a onClick={() => onTodoClick(todo)}>{todo.text}</a></li>
    )}
  </ul>

Do NOT do

const Todos = (props) =>
  <ul>
    {props.todos.map((todo) =>
      <li><a onClick={() => props.onTodoClick(todo)}>{todo.text}</a></li>
    )}
  </ul>

Spread syntax

Use spread syntax instead of Function.prototype.apply.

Do

const myFunction = (x, y, z) => { /* ... */ }
const args = [0, 1, 2]
myFunction(...args)

Do NOT do

const myFunction = (x, y, z) => { /* ... */ }
const args = [0, 1, 2]
myFunction.apply(null, args)

The spread syntax is also a better way to both copy and concatenate arrays and objects.

Do

const arr = [0, 1, 2]
const arr2 = [...arr]
arr2.push(3)
const obj = {x: 1}
const obj2 = {...object, y: 2}
const obj2.z = 3

Do NOT do

const arr = [0, 1, 2]
const arr2 = arr.slice()
arr2.push(3)
const obj = {x: 1}
const obj2 = Object.assign({y: 2}, obj)
const obj2.z = 2

Variables

Const

Use const whenever possible. Note that you can use const even for arrays and objects as long as you do not assign a new reference to that variable.

Do

const arr = [0, 1, 2]
arr.push(3)

Do NOT do

let arr = [0, 1, 2]
arr.push(3)

Let

Use let when you need a non-constant local variable.

Do

for (let i = 0; i < arr.length; i++) {
  // ...
}

Do NOT do

for (var i = 0; i < arr.length; i++) {
  // ...
}

Note: The traditional var should be avoided at all cost.

Functional style

Write functional JavaScript when possible, but keep in mind that some times you are better off with object-oriented JavaScript. Avoid writing imperative JavaScript at all cost.

Do

const capitalize = (str) =>
  [str.charAt(0).toUpperCase(), str.substr(1)].join('')

const processWords = (fn, str) =>
  str.split(' ').map(fn).join(' ')

console.log(processWords(capitalize, 'The quick brown fox jumps over the lazy dog'))

Do NOT do

const capitalizeWords = (str) => {
  let result = []
  const arr = str.split(' ')
  for (let i = 0; i < arr.length; i++) {
    result.push(arr[i].charAt(0).toUpperCase() + arr[i].substring(1))
  }
  return result.join(' ')
}

console.log(capitalizeWords('The quick brown fox jumps over the lazy dog'))

Modules

Use ES6 modules instead of CommonJS or AMD modules because it is the standard way of defining modules.

Do

export default () => 'Hello from an ES6 module'

Do NOT do

module.exports = () => 'Hello from an old module'