5 ways to refactor if/else statements in JS functions
Tags: webdev, javascript, codenewbie, react
Published: 2020-11-16 23:24:20


In this blog post I will present 5 ways to declutter your code getting rid of unnecessary `if-else` statements. I will talk about:

- [default parameters](#1-default-parameters), 
- [or (||) operator](#2-or-operator), 
- [nullish coalescing](#3-nullish-coalescing), 
- [optional chaining](#4-optional-chaining),
- [no-else-returns and guard clauses](#5-noelsereturns-and-guard-clauses)

***

# 1. Default parameters

You know that feeling when you're working with inconsistent API and your code breaks because some values are `undefined`? 
```js
 let sumFunctionThatMayBreak = (a, b, inconsistentParameter) => a+b+inconsistentParameter

sumFunctionThatMayBreak(1,39,2) // => 42
sumFunctionThatMayBreak(2,40, undefined) // => NaN
```
I see that for many folks the instinctive solution to that problem would be adding an `if/else` statement:
```js
 let sumFunctionWithIf = (a, b, inconsistentParameter) => {
    if (inconsistentParameter === undefined){
      return a+b
    } else {
     return a+b+inconsistentParameter
    }
}

sumFunctionWithIf(1,39,2) // => 42
sumFunctionWithIf(2,40, undefined) // => 42
```
You could, however, simplify the above function and do away with the `if/else` logic by implementing [default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters):

```js
 let simplifiedSumFunction = (a, b, inconsistentParameter = 0) => a+b+inconsistentParameter

simplifiedSumFunction(1, 39, 2) // => 42
simplifiedSumFunction(2, 40, undefined) // => 42
```

# 2. OR operator
The above problem not always can be solved with default parameters. Sometimes, you may be in a situation when you need to use an `if-else` logic, especially when trying to build conditional rendering feature. In this case, the above problem would be typically solved in this way:
```js
let sumFunctionWithIf = (a, b, inconsistentParameter) => {
    if (inconsistentParameter === undefined || inconsistentParameter === null || inconsistentParameter === false){
      return a+b
    } else {
     return a+b+inconsistentParameter
    }
}

sumFunctionWithIf(1, 39, 2) // => 42
sumFunctionWithIf(2, 40, undefined) // => 42
sumFunctionWithIf(2, 40, null) // => 42
sumFunctionWithIf(2, 40, false) // => 42
sumFunctionWithIf(2, 40, 0) // => 42
/// 🚨🚨🚨 but:
sumFunctionWithIf(1, 39, '') // => "40"
```

or this way:
```js
  let sumFunctionWithTernary = (a, b, inconsistentParameter) => {
    inconsistentParameter = !!inconsistentParameter ? inconsistentParameter : 0
    return a+b+inconsistentParameter
}

sumFunctionWithTernary(1,39,2) // => 42
sumFunctionWithTernary(2, 40, undefined) // => 42
sumFunctionWithTernary(2, 40, null) // => 42
sumFunctionWithTernary(2, 40, false) // => 42
sumFunctionWithTernary(1, 39, '') // => 42
sumFunctionWithTernary(2, 40, 0) // => 42
```

However, you could simplify it even more so by using the [OR (`||`) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR). The `||` operator works in the following way:
- it returns the right-hand side when the left-side is a `falsey` value;
- and returns the left-side if it's `truthy`.

The solution could then look as following:

```js
  let sumFunctionWithOr = (a, b, inconsistentParameter) => {
    inconsistentParameter = inconsistentParameter || 0
    return a+b+inconsistentParameter
}

sumFunctionWithOr(1,39,2) // => 42
sumFunctionWithOr(2,40, undefined) // => 42
sumFunctionWithOr(2,40, null) // => 42
sumFunctionWithOr(2,40, false) // => 42
sumFunctionWithOr(2,40, '') // => 42
sumFunctionWithOr(2, 40, 0) // => 42
```

# 3. Nullish coalescing

Sometimes, however, you do want to preserve `0` or `''` as valid arguments and you cannot do that with the `||` operator, as visible in the above example. Fortunately, starting with this year, JavaScript gives us access to the `??` ([nullish coalescing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)) operator, which **returns the right side only when the left side is `null` or `undefined`**. This means that if your argument is `0` or `''`, it will be treated as such. Let's see this in action:

```js
  let sumFunctionWithNullish = (a, b, inconsistentParameter) => {
    inconsistentParameter = inconsistentParameter ?? 0.424242
    return a+b+inconsistentParameter
}

sumFunctionWithNullish(2, 40, undefined) // => 42.424242
sumFunctionWithNullish(2, 40, null) // => 42.424242
/// 🚨🚨🚨 but:
sumFunctionWithNullish(1, 39, 2) // => 42
sumFunctionWithNullish(2, 40, false) // => 42
sumFunctionWithNullish(2, 40, '') // => "42"
sumFunctionWithNullish(2, 40, 0) // => 42
```

# 4. Optional chaining

Lastly, when dealing with inconsistent data structure, it is a pain to trust that each object will have the same keys. See here:

```js
  let functionThatBreaks = (object) => {
    return object.name.firstName
  }

  functionThatBreaks({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1}) // ✅ "Sylwia" 
  functionThatBreaks({id:2}) // 🚨 Uncaught TypeError: Cannot read property 'firstName' of undefined 🚨 
```
This happens because `object.name` is `undefined` and so we cannot call `firstName` on it.

Many folks approach such a situation in the following way:

```js
  let functionWithIf = (object) => {
    if (object && object.name && object.name.firstName) {
      return object.name.firstName
    }
  }

  functionWithIf({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1) // "Sylwia"
  functionWithIf({name: {lasName: "Vargas"}, id:2}) // undefined
  functionWithIf({id:3}) // undefined
  functionWithIf() // undefined
```

However, you can simplify the above with the new fresh-off ECMA2020 JS feature: `optional chaining`. Optional chaining checks at every step whether the return value is `undefined` and if so, it returns just that instead of throwing an error.

```js
  let functionWithChaining = (object) => object?.name?.firstName 

  functionWithChaining({name: {firstName: "Sylwia", lasName: "Vargas"}, id:1}) // "Sylwia"
  functionWithChaining({name: {lasName: "Vargas"}, id:2}) // undefined
  functionWithChaining({id:3}) // undefined
  functionWithChaining() // undefined
```

# 5. No-else-returns and guard clauses

Last solution to clunky `if/else` statements, especially those nested ones, are [no-else-return statements](https://eslint.org/docs/rules/no-else-return)  and [`guard clauses`](https://anthonygharvey.com/ruby/guard_clauses_vs_nested_if_statements). So, imagine we have this function: 
```js 
  let nestedIfElseHell = (str) => {
    if (typeof str == "string"){
      if (str.length > 1) {
        return str.slice(0,-1)
      } else {
        return null
      }
    } else { 
      return null
    }
  }

nestedIfElseHell("") // => null 
nestedIfElseHell("h") // => null
nestedIfElseHell("hello!") // => "hello"
```

## ✨ no-else-return

Now, we could simplify this function with the `no-else-return` statement since all we are returning is `null` anyway:

```js 
  let noElseReturns = (str) => {
    if (typeof str == "string"){
      if (str.length > 1) {
        return str.slice(0,-1)
      }
    }

    return null
  }

noElseReturns("") // => null 
noElseReturns("h") // => null
noElseReturns("hello!") // => "hello"
```

The benefit of the `no-else-return` statement is that if the condition is not met, the function ends the execution of the `if-else` and jumps to the next line. You could even do without the last line (`return null`) and then the return would be `undefined`. 

psst: _I actually used a no-else-return function in the previous example_ 👀

## ✨ guard clauses

Now we could take it a step further and set up guards that would end the code execution even earlier:

```js 
  let guardClauseFun = (str) => {
    // ✅ first guard: check the type
    if (typeof str !== "string") return null
    // ✅ second guard: check for the length
    if (str.length <= 3) console.warn("your string should be at least 3 characters long and its length is", str.length) 
    // otherwise:
    return str.slice(0,-1)
  }

guardClauseFun(5) // => null 
guardClauseFun("h") // => undefined with a warning
guardClauseFun("hello!") // => "hello"

```

***

What tricks do you use to avoid clunky if/else statements?

✨✨✨  If you are comfortable with OOP JS, definitely check [this awesome blog post](https://dev.to/mcsee/how-to-get-rid-of-annoying-ifs-forever-1jfg) by Maxi Contieri! 

***

Cover photo by [James Wheeler from Pexels](https://www.pexels.com/@souvenirpixels?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)