Stop using arrow functions everywhere ...
Published: 2021-11-02 04:29:52

Ever since their inclusion in JavaScript, arrow functions' usage has increased at a very rapid rate by JavaScript developers. Everyone seems to like arrow functions; perhaps more than they really should.

Firstly, there are situations where one absolutely should **not** use an arrow function.

> Before going further, it is important to understand that arrow functions are **not** just a syntactical sugar for normal functions. Arrow functions, apart from having a different syntax for declaration, also behave differently than normal functions. One of the key differences is that they have no knowledge of the *execution context*. The execution context is dynamic (assuming your application has function calls) and is accessed through the `this` keyword in JavaScript. This is why it is often said that arrow functions do not have their own binding to `this`. They inherit `this` from their enclosing scope. This difference is at the heart of understanding when not to use arrow functions.
>
>```js
> window.id = 107;
>   
> const obj = {
>   id: 108,
>   normalFunction: function () { console.log(this.id) },
>   arrowFunction: () => { console.log(this.id) },
> }
> 
> obj.normalFunction(); // output: 108
> obj.arrowFunction(); // output: 107
>```
>
> The reason `arrowFunction()` logs 107 is that its declaration (as a method of `obj`) is in the global scope which, in the case of a browser environment, is the `window` object. So `this` resolves to window inside `arrowFunction()`.

## Do not use arrow functions ...

### ... when defining object methods

Using arrow functions to define object methods may result in unexpected (buggy?) behavior. Chances are that if you're defining an object method, you need access to to its properties from within that method.

```js
const counter = {
  count: 0,
  currentCount: function () { return this.count },
  incrementCount: function () { ++this.count }
}

counter.incrementCount();
console.log(counter.currentCount()); // output: 1
```

Everything is good and works as expected. But, if we had used arrow functions here:

```js
const counter = {
  count: 0,
  currentCount: () => { return this.count },
  incrementCount: () => { ++this.count }
}

counter.incrementCount(); // this does nothing unfortunately
console.log(counter.currentCount()); // output: NaN
```

`this` resolves to the `window` object in the above case and `window.count` doesn't exist. If it is conciseness you're after, you can use the new shorthand syntax for defining object methods. The code below is equivalent to the first example above:

```js
const counter = {
  count: 0,
  currentCount() { return this.count },
  incrementCount() { ++this.count }
}

counter.incrementCount();
console.log(counter.currentCount()); // output: 1
```

### ... when defining prototype methods

This case is conceptually similar to the one discussed above. If you wish to access an instance property, you must not use an arrow function when defining the prototype method.

```js
function Person(name) {
  this.firstName = name;
}

Person.prototype.normalGreet = function () {
  console.log(`Hi ${this.firstName}`);
}

Person.prototype.arrowGreet = () => {
  console.log(`Hi ${this.firstName}`)
}

const person1 = new Person("Jack");

person1.normalGreet(); // output: "Hi Jack"
person1.arrowGreet(); // output: "Hi undefined"
```

As you can see, `this.firstName` inside the normal function resolves successfully to `"Jack"` while in the case of the arrow function it returns `undefined`. This is because inside the arrow function `this` points to the function's enclosing scope which is the `window` object in this case. Since `window.firstName` is undefined, it returns `undefined`.

### ... when defining any function that depends on the dynamic context

The recurring theme is that arrow functions have no knowledge of the (dynamic) execution context and, hence, should not be used when the execution context is a factor. Another example of this would be callbacks that depend on the dynamic context like event handlers.

```js
someButton.addEventListener("click", () => {
  console.log(this.innerText);
})
```

In the code example above, clicking on `someButton` will log `undefined` since `this` does **not** refer to the `currentTarget`.

Furthermore, `call`, `apply`, and `bind` do not work with arrow functions.

### ... when defining a constructor function

Arrow functions can't be used as a constructor function (can't use `new` with them). It will simply raise an error.

```js
const Person = (name) => {
  this.firstName = name;
}

const person1 = new Person(); // TypeError: Person is not a constructor
```

### ... if you wish to use the special `arguments` object

Just like with `this`, arrow functions do not have their own `arguments` object. Instead, `arguments` inside an arrow function refers to the *arguments of the enclosing scope*.

```js
function greet(name) {
  const constructGreeting = (greeting) => {
    return `${greeting} ${arguments[0]}`;
  }
  
  return constructGreeting("hello");
}

console.log(greet("Jack")); // output: "hello Jack"
```

As you can see, `arguments[0]` inside the arrow function `constructGreeting()` actually resolves to the value of `name` which is the argument to `greet()` (the function that encloses `constructGreeting()`).

## It's not all bad

There are scenarios where arrow functions provide benefit. Mostly by making the code cleaner/intuitive.

### Async callbacks

Imagine you have async code but you still need access to the original context that called the async function (from within the async callback). Normal functions wouldn't work here (if you used `this` inside the async callback). This issue was previously solved by using `bind` or declaring a `self` variable where `self = this`. But since `this` is already lexically bound in an arrow function, the arrow function solves this problem elegantly. An example should clear things up.

```js
const obj = {
  firstName: "Jack",
  useName: function () {
    setTimeout(function () {
      console.log(`Hi ${this.firstName}`);
    }, 1000);
  }
}

obj.useName(); // output after 1 second: "Hi undefined"
```

In the example above, I've used `setTimeout()` to simulate asynchrony. The callback function passed to `setTimeout()` is called after 1 second of calling `obj.useName()`. By the time the callback function is called, `useName()` has long been executed. In fact, the callback queue is executed in the global context (`window` in case of our browser environment). This means that `this = window` when the async callback is called. This is why `this.firstName` resolves to `undefined`.

As I stated above, before arrow functions, this problem was solved by:

* using `bind`
  ```js
  const obj = {
    firstName: "Jack",
    useName: function () {
      setTimeout(function () {
        console.log(`Hi ${this.firstName}`);
      }.bind(this), 1000);
    }
  }

  obj.useName(); // output after 1 second: "Hi Jack"
  ```

* by declaring a referencing variable (traditionally called `self`)
  ```js
  const obj = {
    firstName: "Jack",
    useName: function () {
      let self = this;
      setTimeout(function () {
        console.log(`Hi ${self.firstName}`);
      }, 1000);
    }
  }

  obj.useName(); // output after 1 second: "Hi Jack"
  ```

Now we can simply use an arrow function which gives us the expected results.

```js
const obj = {
  firstName: "Jack",
  useName: function () {
    setTimeout(() => {
      console.log(`Hi ${this.firstName}`);
    }, 1000);
  }
}

obj.useName();
```

### Callbacks for iterator functions

When I say iterator functions I mean functions like `map()`, `reduce()`, and `forEach()` that go through each item of an iterable and feed that item to a callback function. This is where arrow functions really shine. No need to worry about the dynamic context. Additionally, the compact syntax and the implicit return feature of arrow functions often result in code that is concise and more readable at the same time.

```js
const myArray = [2, 4, 6, 8];

const newArray = myArray.map(number => number * 2);

console.log(newArray); // output: [4, 8, 12, 16]
```

## In my humble opinion ...

Although arrow functions are a great addition to JavaScript, and in the proper context their concise syntax improves code readability, I still don't think that they should be the default for function declaration. If the situation doesn't specifically call for it, there is no need to use an arrow function.

Consider, for example, your globally-scoped or module-scoped functions (perhaps you have a `helpers.js` file that has a bunch of helper functions your application needs). The arrow function syntax in this case simply looks more cluttered than a normal function declaration. On top of it, the code becomes even more cluttered if you have `async` functions and need to `export` some of them.

```js
export async function foo() {
  // function body
}

export const bar = async () => {
  // function body
}
```

*Using arrow functions:*

```js
export const firestore = firebase.firestore();
export const auth = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();

export const signInWithGoogle = () => auth.signInWithPopup(provider);

export const getUserDocument = async (uid) => {
  // body
}

export const createUserDocument = async (user, additionalData) => {
  // body
}
```

*Using normal functions:*

```js
export const firestore = firebase.firestore();
export const auth = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();

export function signInWithGoogle() {
  auth.signInWithPopup(provider);
}

export async function getUserDocument(uid) {
  // body
}

export async function createUserDocument(user, additionalData) {
  // body
}
```

The normal function syntax is just better. It reads better and it is actually less typing. Not that less typing should be a factor here but I've often observed that it is a factor behind using arrow functions.

Another example, which is slowly becoming a pet peeve of mine, is using arrow functions for declaring a React funtional component. There is just no need to do that! You can simply use a normal function declaration.

*Using an arrow function:*
```js
export default const UserCard = (props) {
  // a ReactJS functional component -- arrow edition
}
```

*Using a normal function:*

```js
export default function UserCard(props) {
  // a ReactJS functional component -- normal edition
}
```

Isn't a normal function delcaration much clearer?

---

👉🏻 Subscribe to my newsletter: [click here](https://www.bracketsinstitute.com/newsletter-subscription)

👉🏻 Follow me on twitter: [click here](https://twitter.com/therealnrf)

---