13

I have code like this. How can I write it in cleaner, more elegant way using functional programming in JavaScript? I want to get rid of nested ternary expressions. Any ideas?

props => ({
            iconColor: props.isPriority ? (props.isCompleted ? variables.color.lightpurple : variables.color.purple ) : variables.color.gray3,
            iconName: props.isPriority ? 'star-full' : 'star-empty',
          }))

This is rest of that code:

EDIT:

const enhance: React$HOC<*, InitialProps> = compose(
      withProps(props => ({
        iconColor: props.isPriority ? (props.isCompleted ? variables.color.lightpurple : variables.color.purple) : variables.color.gray3,
        iconName: props.isPriority ? 'star-full' : 'star-empty',
      }))
    )
MountainConqueror
  • 592
  • 3
  • 7
  • 27

5 Answers5

21

Yes, but my linter is not happy: 44:16 error Do not nest ternary expressions [no-nested-ternary]

If that's your only problem then the solution is simple. Create your own conditional function:

const iff = (condition, then, otherwise) => condition ? then : otherwise;

props => ({
  iconColor: props.isPriority ?
    iff(props.isCompleted, variables.color.lightpurple, variables.color.purple) :
    variables.color.gray3,
  iconName: props.isPriority ? 'star-full' : 'star-empty',
})

Now your linter shouldn't complain. That being said, you should disable [no-nested-ternary] in your linter. It's kind of stupid that your linter thinks that nested conditionals are bad.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 2
    This is just tricking ESLint and adding indirection. I agree that this rule should disabled if no alternatives are suitable. Can you please edit the answer to include a bit more promenant solution of changing the eslint config instead of adding function that doesn't benefit the codebase in any way and just makes it harder to read? – Cedomir Rackov May 09 '20 at 09:45
  • 5
    It is not "kind of stupid", the linter is not complaining about nested conditionals, is complaining because nested ternary operators are generally bad for code clarity. – Eric Van Der Dijs Jul 23 '21 at 00:36
  • @Eric Van Der Dijs That's debatable. Coming from a functional programming background I think nested conditionals are all right for code clarity until it gets too unwieldy. It should be upto the team of programmers to decide what is considered good code and what is considered bad code. The linter should not force its preferred style upon the programmer. – Aadit M Shah Jul 26 '21 at 04:13
  • Note that I've never said "nested conditionals are bad for clarity". I only spoke about writing nested conditionals **using** a ternary operator (i.e: condition ? 'foo' : anotherConditon ? 'bar' : 'baz'), but of course, the code style depends entirely of your team decisions, I'm only talking about a common standard. – Eric Van Der Dijs Jul 27 '21 at 21:44
0

I agree with @JaromandaX about using a function. That keeps your JSX clean and your logic reusable.

Apart from that, to avoid nested ternary operator, you can try something like this:

  • Keep an array/map of all possible values
  • Based on flags, create a binary string and convert it into number.
  • return the value at provided index

function withTernary(a, b){
  return a ? (b ? 'test': 'foo') : 'bar';
}

function withoutTernary(a, b){
  var result = ['bar', undefined, 'foo', 'test'];
  // or may be a map
  /*
  * map = {
  *    '00': 'bar',
  *    '10': 'foo',
  *    '11': 'test'
  * }
  */
  var index = parseInt((+a) + '' + (+b), 2);
  return result[index];
}

var testCase = [[0,0], [1, 0], [1,1]];

testCase.forEach(x => {
  console.log('With Ternary:', withTernary(...x));
  console.log('Without Ternary:', withoutTernary(...x));
})
Rajesh
  • 24,354
  • 5
  • 48
  • 79
  • 2
    **Note:** This is an alternate approach for avoiding nested ternary operator. If you think anything in this answer is *unnecessary/ not required/ improper*, please comment along with your vote. Have a good day. – Rajesh Sep 18 '17 at 05:41
0

One could use an IIaFE ( immeadiately invoked arrow function expression) :

props => ({
        iconColor:(_=>{
           if(props.isPriority){
             if(props.isCompleted){
               return variables.color.lightpurple;
             }
             return variables.color.purple;
           }
           return variables.color.gray3;
        })(),
        iconName:(_=>{ 
           if(props.isPriority){
              return 'star-full';
           }
           return 'star-empty';
       })()
}))
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • I don't think, we need arrow function as we are not using `this` in it. Also, having a predefined function is always better than having an inline function, as it adds a possibility of reusing it. – Rajesh Sep 18 '17 at 05:54
  • IIFEs are not particularly functional. Instead use a reusable function like `const apply = (...args) => f => f(...args)`. The code gets easier to follow and you can even destructure in the arguments of `f`: `apply(props) (({isPriority, isCompleted}) => { switch (isPriority) {...}})`. –  Sep 18 '17 at 11:12
0

In this case you could consider inverting the condition to remove the "unnatural" nesting of ternaries. There would still be nesting, but it can be written flat - without parentheses - which means that you can lay it out neatly across multiple lines:

props => ({
    iconColor:
        !props.isPriority ? variables.color.gray3
        : props.isCompleted ? variables.color.lightpurple
        : variables.color.purple,
    iconName: props.isPriority ? 'star-full' : 'star-empty',
})

The downside of this is use of a negative condition, which I usually try to avoid as they're a bit harder to follow than positive conditions.

Yet another option is to flatten the conditions with &&:

props => ({
    iconColor:
        props.isPriority && props.isCompleted ? variables.color.lightpurple
        : props.isPriority ? variables.color.purple
        : variables.color.gray3,
    iconName: props.isPriority ? 'star-full' : 'star-empty',
})
TheQuickBrownFox
  • 10,544
  • 1
  • 22
  • 35
0

I will suggest, simply use below:

    props => ({
      iconColor: ${(props) => {
      if (props.isPriority) {
        return props.isCompleted
          ? variables.color.lightpurple
          : variables.color.purple;
      }
      return variables.color.gray3;
    }};
      iconName: props.isPriority ? 'star-full' : 'star-empty',
    })
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 26 '22 at 19:03