Skip to content

Use R.maybe instead of nested ternaries

Dejan Toteff edited this page Jun 10, 2020 · 1 revision

It is a common ESLint rule to warn for nested ternaries. Even if it wasn’t such rule available, it is a bad practice and it hurts code’s readability.

const foo = x > 3 ?
  7 :
  x > 10 ?
    5 : 
    6

One simple way to solve it with if/else statements or usage of switch operator.

Other way to solve the above code is with use of Babel’s do expressions :

let foo = do {
  if(x > 3) {
    7
  } else if(x > 10) {
    6
  }else{
    5
  }
}

Still, we exchange nested ternary for if/else blocks. It seems like a bad deal, as we usually use ternary to escape the ifs.

Ramda is not much of a help in such simple scenario, as its method ifElse is suitable for more complex cases. So in our case, we are forced to create three unneeded closures.

Moreover, if we are using Typescript, then we also have to include an initial argument that would never be used:

const foo = R.ifElse(
  () => x > 3,
  () => 7,
  () => x > 10 ? 6 : 5
)({})

Solution I was in the middle of something, when I realized that I don’t have a good option on hand. But instead of moving forward, I took a step back and created this simple Rambdax module:

export function maybe(ifCondition, whenIf, whenElse){
  return ifCondition ? whenIf : whenElse
}

Absolutely boring stuff as we do nothing more than to hide the ternary inside R.maybe method.

But this allow us to rewrite the code as follows:

const foo = R.maybe(
  x > 3,
  7,
  x > 10 ? 6 : 5
)

Now, this is what I call a readable code.

It would be even better if I replace R.ifElse with R.maybe, but this would be hijacking the Ramda namespace. Last time I did that was with R.is and the guys from WatermelonDB complained as they were dependent on this method.

Further development I am writing this paragraph a week after the initial publication.

I have a use case where I am using foo.bar as third argument, i.e. it will be executed only if the condition(the first argument) evaluates to false.

const foo = undefined
const ant = R.maybe(
  foo === undefined,
  false,
  foo.bar
) 

The above code with throw type error cannot read property bar of undefined.

The solution was to wrap foo.bar as anonymous function () => foo.bar

This required to change R.maybe to accept such functions as second and third argument. The condition here is that these function shouldn’t expect any arguments.

const foo = []
const ant = R.maybe(
  Array.isArray(foo),
  () => foo.length,
  () => foo.bar
) 

R.switcher

Actually Rambdax already has a method that can do the job, but this method, just like R.ifElse, is better suited for more complex logic and again we have closures we don’t need and input that will not be used:

const foo = R.switcher(null) .is(() => x > 10, 6) .is(() => x > 3, 7) .default(5)