March 20, 2021

TLTR; Declarative UI

Declarative programming makes your code more concise, easier to read and reason about and reduces side effects.

JavaScript has function scope, meaning that any variable you declare inside a function is not accessible outside this function:

Procedural and object-oriented are forms of imperative programming, which implement algorithms in explicit steps. React embraces functional programming which is a form of declarative programming.

You describe what the program must accomplish for solving a problem, rather than describing how to accomplish it.

Practical tips for writing declarative code:

  • Avoid loops, use array methods instead
  • Avoid variables, try to compose functions instead
  • Avoid mutating state, create new instances instead
  • Avoid side effects, and ensure your functions are pure
  • Work with the state, don’t manipulate DOM directly

Programming declarative UI is difficult. It requires abstract DOM updates because touching the DOM is a side-effect.

React is a declarative way of writing UIs. It gives you all the tools to write your markup in plain JavaScript functions. You care only about the application state and how this changes when certain events occur. Then it updates the DOM for you in an easy and predictable way.


Introduction

When you start your programming studies the first thing they teach you is variables, logical expressions and loops. Then you begin with procedural and then object-oriented programming. In other words, you learn how to imperatively write code.

This is not necessarily a bad thing. You must get familiar with how computers understand our world. In fact, declarative approaches have some sort of imperative abstraction layer underneath.

Once you start building commercial applications though things get more complex. Simplicity is king. It is essential for every developer in a team to follow the code and reason about. Our goal is to leave these machine instructions behind as much as we can and focus on writing code more perceivable by humans.

In this article, I will explain what declarative programming is and how it applies to web applications. We will see some real-life examples to help you recognize the difference between imperative.

Learning how to write declarative code completely changed my mind about programming. I hope the techniques in this article will help you as much as they helped me.

Excited? So grab a cup of ☕ coffee and come back. I will be waiting.

Humans think declaratively

At the beginning of this chapter, I gave you the following instruction:

> Grab a cup of coffee and come back

This is a declarative approach.

The imperative approach would be something like this:

> Go to the kitchen
> Turn on the coffee machine
> Create an instance of a cup
> Put the cup under the coffee machine
> Press the Espresso button
> Wait for the process to finish
> Turn off the machine
> Take the cup of coffee
> Come back to this screen

We can put this algorithm to a makeCoffee() function or create a fancy Coffee.makeCoffee() method, but the result will be the same. My life would be much easier if I just told you what to do instead of instructing you in the steps above.

Now let’s have a look at how this translates to coding.

Coding in a laptop

A declarative code example

We have the following array of months:

const months = ['January', 'February', 'March', ...]

Let’s imagine that we have to create a calendar similar to the one we find in iOS:

DevTools Profiler

Our first step is to modify our array to have only the first three letters of the month’s name.

Here’s a comparison between the two approaches:

// Imperative code
function getMonths(months) {
  let shortMonths = []

  for (const i = 0; i < months.length; i++) {
    shortMonths.push(months[i].slice(0, 2))
  }

  return shortMonths
}

// Declarative code
function getMonths(months) {
  return months.map((month) => month.slice(0, 2))
}

In this second approach, we leverage the map() array method and thus we avoid looping over the array items.

We avoid unnecessary variable declarations, which means less code to work with and this makes the overall approach easier to read.

We avoid touching the DOM, which means our function has no side-effects. It is also pure, with the same input it will always return the same output.

Finally, we avoid using the array.push() method which mutates our array.

It seems we’ve accomplished quite a lot in such a simple task, right? Imagine how easier our programs would be once we start applying these simple practices as much as we can.

Now let’s see how this affects the UI.

Declarative UIs

HTML is declarative by design. Consider the following markup:

<ul class="months">
  <li><button>01</button></li>
  <li><button>02</button></li>
  <li><button>03</button></li>
  ...
</ul>

You don’t need to explicitly describe step by step how the browser will be rendering this list on the screen.

CSS works similarly, the style of the class .months will be applied automatically to all the matching DOM elements which have this class.

The problem starts when you add interactivity.

Airbnb calendar The Airbnb calendar allows you to select a date period

Consider this Airbnb calendar. Two things happen when you click on a day. At first, we indicate the selected days with a black circle. Also the check in/out field gets updated with the new date range.

Below is an example of this in modern JavaScript. For the sake of simplicity, we use the class .selected to indicate one selected day at a time.

class Calendar {
  constructor(domId) {
    this.renderCalendar(domId)    this.addEventListeners(domId)  }

  handleCalendarClick(selectedDay) {
    event.preventDefault()
    this.updateView(selectedDay)
    this.updateInput(selectedDay)
  }

  addEventListeners(domId) {
    document
      .getElementById(domId)
      .querySelectorAll('li button')
      .forEach((button) => {
        const selectedDay = button.dataset.day
        button.addEventListener('click', () => {
          this.handleCalendarClick(selectedDay)
        })
      })
  }

  updateView(selectedDay) {
    const selector = `button[data-day="${selectedDay}"]`
    document.querySelector().classList.toggle('selected')
  }

  updateInput(selectedDay) {
    document.getElementById('selectedDate').value = selectedDay
  }

  renderCalendar(domId) {
    document.getElementById(domId).innerHTML = `
			<input id="selectedDate" />
				<ul>
					<li>
						<button data-day="01">01</button>
					</li>
					<li>
						<button data-day="02">02</button>
					</li>
					...
				</ul>
			`
  }
}

Try this example by yourself in JSFiddle.

As you can see this approach is imperative:

  • A big part of the Calendar class explicitly modifies DOM elements.
  • We depend on the data-day attribute to get the selected button
  • We no longer give the browser what to render, we instructively tell the browser how to modify the DOM.

What if we could write our application like this:

<SearchFilters>
  <DateRangeInput value={dateRange} />
  <Calendar value={dateRange} onChange={setDateRange} />
</SearchFields>

Here’s how it works:

  • <Calendar> displays a calendar and uses dateRange to mark the selected days. It also allows the user to modify their selection. This happens by calling the onChange() function with the updated dateRange value.
  • <DateRangeInput> is a special kind of input that displays the selected date range. Similar to the calendar component it can also modify the selection as the user types.
  • <SearchFilters> is the container div that applies the layout.

Modern UI libraries let you do exactly

React

React went popular because it lets you work with the state of your UI and it abstracts all DOM updates in a fast and predictable way. In fact, the development experience is so good that people like to write CSS in JS as well.

Think of it as a simple function:

render(state)

You are responsible to describe how your UI is being rendered to the DOM by a given state input. This object contains properties or methods that let your UI function inform you about certain events, for example, a mouse click.

For every event that occurs we update the state and this forces the application to render again:

event => state update => render

This unidirectional data flow helps you build incredibly simple applications that are easy to test and maintain.

Remember this

Your aim as a React developer is to write declarative code as much as you can. It’s like learning how to drive a supercar.

But how we can declaratively change the DOM and maintain good performance? What did you say? What is the DOM? OK this will take another coffee.