Javascript: About no-nested-ternary

Created on 25 Mar 2016  路  12Comments  路  Source: airbnb/javascript

I sometimes use the following syntax that I find elegant and useful.
What do you think about it? And What do you suggest to replace it?

var foo =
  condition1 ? a :
  condition2 ? b :
  condition3 ? c :
  d;

Most helpful comment

@ggomesfe In your example above:

var foo = d;
if (condition1) {
    foo = a;
} else if (condition2) {
    foo = b;
} else if (condition3) {
    foo = c;
}

we need to have useless mutation and cannot use const that we could use in:

const foo =
  condition1 ? a :
  condition2 ? b :
  condition3 ? c :
  d;

which have semantic consequences that are beyond mere style - i.e. the variable is no longer protected from being reassigned - an otherwise promoted property in the spirit of prefer-const rule described in https://github.com/airbnb/javascript#variables

To use your example to assign to a const we would need to either use a closure:

const foo = (() => {
  if (condition1) {
    return a;
  } else if (condition2) {
    return b;
  } else if (condition3) {
    return c;
  } else {
    return d;
  }
})();

or use a temporary variable:

let fooTemp = d;
if (condition1) {
  fooTemp = a;
} else if (condition2) {
  fooTemp = b;
} else if (condition3) {
  fooTemp = c;
}
const foo = fooTemp;

Both of those workarounds seem to be much less clear than the use of the ternary operator for what it was designed for.

All 12 comments

You can use an if/else statement.

var foo;
if (condition1) {
    foo = a;
} else if (condition2) {
    foo = b;
} else if (condition3) {
    foo = c;
} else {
    foo = d;
}

A lot more verbose and redondant. Don't you think ?

For this particularly contrived example yes. In practice the if/else statement is more readable even if its more verbose.

If you want to assign it to a reference without having to mutate you could abstract it out to a function.

Why not set by default 'd' to 'foo' variable before if/else conditions? That way it's not necessary declare else condition.

var foo = d;
if (condition1) {
    foo = a;
} else if (condition2) {
    foo = b;
} else if (condition3) {
    foo = c;
}

@testerez Have you found any elegant solution to this issue? I recently had a function like this:

const first = (arg, ...args) =>
  (arg != null ? arg : args.length > 0 ? first(...args) : undefined);

which I had to convert to this to satisfy this linter rule:

const first = (arg, ...args) =>
  (arg != null ? arg : (() => (args.length > 0 ? first(...args) : undefined))());

which is not perfect but still better than:

const first = (arg, ...args) => {
  if (arg != null) {
    return arg;
  } else if (args.length > 0) {
    return first(...args);
  } else {
    return undefined;
  }
};

The problem I have with this rule is that it is not really about nesting in a way that we could nest if/else blocks. The use case that it disallows is not analogous to nested if/else but to a flat else-if block, as demonstrated but the structure of explicit if-based blocks that you have to convert it to. It wouldn't be a big deal if the if returned a value but it doesn't so if/else is useless in expression-based arrow functions so instead of:

const f = a => (a < 0 ? 1 : a < 2 ? 1 : a);

i have to use if, therefore I need to use a block of expressions with explicit returns, therefore I need to use parentheses around the argument etc. hitting literally every single linter rule related to arrow functions that we have here. Now this is valid but I wouldn't necessarily consider it more readable:

const f = a => (a < 0 ? 1 : (() => (a < 2 ? 1 : a))());

That's why I'm asking if you found a more elegant solution to that.

In fact the one with the if statements is much better - clarity is far more important than brevity.

@rsp In all your examples I also found that this one is the better:

const first = (arg, ...args) => {
  if (arg != null) {
    return arg;
  } else if (args.length > 0) {
    return first(...args);
  } else {
    return undefined;
  }
};

But I personally like:

const first = (arg, ...args) =>
  arg != null ? arg :
  args.length > 0 ? first(...args) :
  undefined;

@testerez Your last example is what I like the most. In fact, multiline ternary is what I find much more readable than the if/elses - it's short, readable, to the point, always evaluates to a single expression, has a very functional programming feel.

In fact, looking at the two examples in your post I find it surprising that the Airbnb style guide which is otherwise very functional-programming-oriented forces me to change the second one to the first one. If anything I would expect it to work the other way around - to recommend the ternary operator if all you have in the if/else blocks are returns.

I would understand if it didn't allow the ternary operator at all but saying that this is readable:

const a = x =>
  x < 2 ? 2 :
  x;

and this is not:

const b = x =>
  x < 2 ? 2 :
  x < 3 ? 1 :
  x;

seems strange to me, especially when compared to the recommended style:

const c = (x) => {
  if (x < 2) {
    return 2;
  } else if (x < 3) {
    return 1;
  }
  return x;
};

I'd like the linter to tell me that I can simplify the last example with a ternary operator, not that I shouldn't use the ternary operator at all (unless it's for a single comparison - in which case it's fine). Unfortunately the linter doesn't share my view on this.

@ljharb I agree that clarity is far more important than brevity - I just don't think that function c() above is more clear that function b(). This is the only issue that I have with those linter rules so far after using them extensively in production for large Node projects for over a year so don't get me wrong. I think you've done an amazing job, many thanks for that. Now, if you just stopped hating my favorite operator it would be perfect. ;)

@ggomesfe In your example above:

var foo = d;
if (condition1) {
    foo = a;
} else if (condition2) {
    foo = b;
} else if (condition3) {
    foo = c;
}

we need to have useless mutation and cannot use const that we could use in:

const foo =
  condition1 ? a :
  condition2 ? b :
  condition3 ? c :
  d;

which have semantic consequences that are beyond mere style - i.e. the variable is no longer protected from being reassigned - an otherwise promoted property in the spirit of prefer-const rule described in https://github.com/airbnb/javascript#variables

To use your example to assign to a const we would need to either use a closure:

const foo = (() => {
  if (condition1) {
    return a;
  } else if (condition2) {
    return b;
  } else if (condition3) {
    return c;
  } else {
    return d;
  }
})();

or use a temporary variable:

let fooTemp = d;
if (condition1) {
  fooTemp = a;
} else if (condition2) {
  fooTemp = b;
} else if (condition3) {
  fooTemp = c;
}
const foo = fooTemp;

Both of those workarounds seem to be much less clear than the use of the ternary operator for what it was designed for.

Instead of just a simple one liner map I'll have to do a long function? How is this more readable or better practice?

@SelaO "shorter" isn't always "more readable". If you're doing a lot of things, it's generally more readable when it's on more lines.

When it comes to legibility, in my opinion it comes down to formatting.

This to me is something that I am struggling with to follow

const foo =
  condition1 ? a :
  condition2 ? b :
  condition3 ? c :
  d;

This however wins over any if-else, every time.

const foo = condition1 
  ? a
  : condition2 
    ? b
    : condition3 
      ? c
      : d;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

danielfttorres picture danielfttorres  路  3Comments

stephenkingsley picture stephenkingsley  路  3Comments

surfaceowl picture surfaceowl  路  3Comments

tunnckoCore picture tunnckoCore  路  3Comments

ar
mbifulco picture mbifulco  路  3Comments