5 ways to refactor if/else statements in JS functions

5 ways to refactor if/else statements in JS functions:

 

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

1. Default Parameters

You know that feeling when you’re working with inconsistent API and your code breaks because some values are undefined?

 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:

 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/elselogic by implementing default parameters:

 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:

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:

  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. 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:

  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) 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:

  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:

  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 firstNameon it.

Many folks approach such a situation in the following way:

  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.

  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 and guard clauses. So, imagine we have this function:

  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:

  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:

  let guardClauseFun = (str) => {
    // ✅ first guard: check the type
    if (typeof str !== "string") return null
    // ✅ second guard: check for the length
    if (str.length  null 
guardClauseFun("h") // => undefined with a warning
guardClauseFun("hello!") // => "hello"

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

from Tumblr https://generouspiratequeen.tumblr.com/post/635053032967700480

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s