Made with Canon 5d Mark III and loved analog lens, Leica APO Macro Elmarit-R 2.8 / 100mm (Year: 1993)
Photo by Markus Spiske / Unsplash

What is this topic about?

If you are from a javascript background you might have heard the terms callback hell or async/await hell. It looks something like this:

The horror.

There is a similar situation with just using if/else as well. You might label this as developers being obsessive, or ignore it by thinking that this is kind of okay in some situations.

I beg to differ. As the saying goes…just pretend that whoever maintains your code next knows where you work and can come yell at you.

For the purpose of this article, I’ll demonstrate an example using ReactJS. The principle itself can be applied in Javascript or any language for that matter.

Before we begin, the <MyButton /> example may not be the best example to explain the if/else nested problem. But hopefully it’ll give you a good guideline as to what the problem is & how to avoid it.

Let’s paint a picture. You are given a button to implement in React & the button has 2 options for a theme, either default or primary. You think it’s simple & you write your <MyButton /> component:

const MyButton = ({ theme, content }) => {
  let className = '';                
  if (theme === 'default') {
    className = 'default-btn';
  } else if (theme === 'primary') {
    className = 'primary-btn';
  }
                   
  return (
    <button className={className}>{content}</button>
  );
}
MyButton.1.jsx

Some time passes & another developer is given a task to add functionality for round corners for the button for both themes, default & primary. The developer who picks up the tasks is very big on using ternary operators. They end up doing something like below:

const MyButton = ({ theme, rounded, content }) => {
  let className = '';                
  if (theme === 'default') {
    className = rounded ? 'default-btn rounded' : 'default-btn';
  } else if (theme === 'primary') {
    className = rounded ? 'primary-btn rounded' : 'primary-btn';
  }
                   
  return (
    <button className={className}>{content}</button>
  );
}
MyButton.2.jsx

Time passes & another developer is given a task to add a hover state for both the default & primary buttons. Now the other developer does not want to make changes in the already code implemented, fearing they might end up breaking something.

So they write a separate if statement:

const MyButton = ({ theme, rounded, hover, content }) => {
  let className = '';                
  if (theme === 'default') {
    className = rounded ? 'default-btn rounded' : 'default-btn';
  } else if (theme === 'primary') {
    className = rounded ? 'primary-btn rounded' : 'primary-btn';
  }
  
  if (hover) {
    className = className + ' hover';
  }
                   
  return (
    <button className={className}>{content}</button>
  );
}
MyButton.3.jsx

So far so good …

This is where it gets interesting

Moving on, a final requirement comes in months later to add an animation when the user hovers over a button which has a primary theme & is of rounded type.

Now based on this requirement, the entire API structure changes the <MyButton/> component. The developer working on the code ends up with logic like this:

const MyButton = ({ theme, rounded, hover, animation, content }) => {
  let className = '';                
  if (theme === 'default') {
    className = rounded ? 'default-btn rounded' : 'default-btn';
    if (hover) {
      className = className + ' hover';
    }
  } else if (theme === 'primary') {
    if (rounded) {
      if (hover) {
        if (animation) {
           className = 'primary-btn rounded hover my-custom-animation';
        } else {
          className = 'primary-btn rounded hover';
        }
      } else {
        className = 'primary-btn rounded';
      }
    } else {
      if (hover) {
        className = 'primary-btn hover';
      } else {
        className = 'primary-btn';
      }
    }
  }

  return (
    <button className={className}>{content}</button>
  );
}
MyButton.4.jsx

That got out of hand way too quickly …. didn’t it?

and you think to yourself, there was nothing you could have done :(

In order to make this code simpler, we need to understand all the possible states that this code has. I have made a possibility chart of all the possible combinations at a certain time for the button.

All the possible combinations of values that <MyButton /> component can have at a time

If this seems a bit complicated, you can try looking at this next chart for your understanding.

This is the same as the previous one, the FALSE values are omitted here for simplicity sake

The key thing when writing code is understanding the data flow of your code. Once you have a complete understanding of it, everything becomes simpler.

Solution

Based on the above given criteria, I can write my code like this to simplify it.

const MyButton = ({ theme, rounded, hover, animation, content }) => {
  const isThemeDefault = theme === 'default'
  const isThemePrimary = theme === 'primary';
  const isRounded = rounded === true;
  const isHover = hover === true;
  const isAnimated = animation === true;
  
  const isPrimaryAnimated = isThemePrimary && isAnimated;
  
  let className = isThemePrimary ? 'primary-btn' : 'default-btn';

  if (isRounded) {
    className = `${className} rounded`;
  }
  if (isHover) {
    className = `${className} hover`;
  }
  if (isPrimaryAnimated) {
    className = `${className} animated`;
  }
 
  return (
    <button className={className}>{content}</button>
  );
}
MyButton.5.jsx (As a bonus feature, it also ends up handling an edge case where, if the wrong theme is passed to MyButton, it ends up ignoring it & assigning it the value of default.

This code is now way more readable. Any developer who works on this code can easily extend its functionality & get on with their life, knowing that they have done a wonderful job with the code.

You can try playing with the code if you want, to see if it matches all the use cases.

codesandbox demo https://codesandbox.io/s/0pl6xvqrnw

With the automata (finite state machines)-like coding approach:

  • Code is more readable now
  • Code is more maintainable

Feel free to share your thoughts. Thank you for reading.

You can also reach me out on twitter @adeelibr

Reference & Inspiration: Stack Exchange Forum