Declarative vs. Imperative Thinking In A JavaScript Context Explained Simply

0 Comments

In a nutshell, imperative programming is when you tell your program every single step to achieve a specific outcome. This means your program have to follow each procedure or else it will fail.

For example, here is an imperative scenario:

You reach for the door
You grasp the door handle
You turn the door handle
You hold the door handle in position
You pull the door handle
The door opens
You walk out the door
You close the door behind you

Failing any one of these steps in the sequence means that your imperative program will result in an error or unexpected outcome. For example, if you turn the door handle but don’t hold it in position, the door may not open when you pull.

This kind of programming requires a particular sequence in order to execute properly. Imperative programming focuses on how the program operates. There is no alternative to the process.

Imperative programming is great for spaces where the sequence is required to achieve a particular result. That’s why it often sits at the hardware level of programming.

This is in contrast with declarative programming. With declarative programming, we only care about what the overall objective of the program will do. It doesn’t matter what order you perform your functions and executions in — only that it produces the expected outcome.

For example, there is no one way to make coffee correctly. There are only requirements such as the water being hot, that there is coffee, sugar, milk, and a spoon to stir everything in. The order of the ingredients doesn’t matter as much. The sugar can go in first or it can go in last — it doesn’t matter. What matters is that the coffee is sweetened at some point.

Why is declarative thinking important in React?

When it comes to React, the mantra you should be adopting is to start with the end in mind. This means that when coding, you shouldn't preoccupy yourself too much with the process of getting to your end goal – but instead focus on what you want to achieve.

This means rather than working on how you want to achieve a particular outcome, focus on what the component should look like in its new state. This is because React doesn't repaint the DOM until a mutation in state has occurred.

For example, here is some imperative thinking applied in JavaScript:

const container = document.getElementById('chicken');
const makeFly = document.createElement('makeFly');

makeFly.className = 'flightless';
makeFly.onclick = function(event){
  if(this.classList.contains('flightless')){
    this.classList.remove('flightless');
    this.classList.add('fly');
  }
}

The above example does the expected result – but it is tightly tied to the dictated process flow. There can be no variation in the program and the JavaScript will execute from top to bottom. To toggle between flying and not flying, we will need another function or condition to dictate the reversed process.

However, take a look at the same concept – but with React and declarative thinking implemented:

class Button extends React.Component{
  this.state = { flight: false }
  
  handleChange = () => {
    const flight = this.state.flight === 'true' ? 'false' : 'true';
    this.setState({ flight });
  }
  
  render() {
    return (<div>
      <button 
         className=`chicken ${this.state.flight}`
         onClick={this.handleChange}>
      </button>
    </div>);
  }
}

While on the surface it looks like more code, the above declarative implementation creates a flexible program that deals with the end result rather than the process. setState finalizes the action and React repaints the DOM once as needed rather than having to change things as they occur.

This is important because React doesn't manipulate the DOM at all, unlike the first example. It just goes by - something has changed, repaint the entire page based on the new states. In contrast to the JavaScript that removes and adds classes.

While on the surface, removing and adding classes in JavaScript doesn't sound all that bad – imagine if you had a few hundred of them to maintain. That means a few hundred potential sequences and variations that you have to mentally keep track of and potentially fix if something goes wrong.

In contrast, React works around the concept of states and only makes a change when the state changes. React doesn't care about how the state changes or what it changes into – only that it has changed. This means that the developer can focus on what the current state is and determine what the next state should.

For example, if we were to accommodate every potential combination of making coffee based on 5 parameters, you'd need to accommodate every sequence and it's incomplete versions. This can be achieved through a series of if-else statements or switch cases.

In contrast, declarative thinking gives you the ingredients and it doesn't the order in which they occur. It can be mixed, matched, or left incomplete. If you use React, the state of each ingredient is centralized, making it trackable over its various transformations over time.

Things to avoid in React to keep your components declarative

  • Avoid communicating between components. This can pollute your state and make it harder to track.
  • Avoid making manipulations to the DOM directly. Just because you can (because React is ultimately JavaScript) doesn't mean you should. The point of React is that it repaints the DOM completely when something changes. When you manipulate the DOM directly, you are breaking the purpose of React's existence.
  • Use props to communicate with other components, and keep your relationships at a sibling level.

And that's basically the foundation of thinking declaratively in React.js.