Modern UI Frameworks: State in Markup

01 Jul 2022
Article image
Photo by Ivan Bandura on Unsplash
  • # Frontend
  • # Web

HTML is a declarative language for defining a UI. With it, you make declarations about the content and structure of a user interface, for example,

<div>
  <span>Game score: 1</span>
</div>

to say "There's a block and inside it, there's the text 'Game score: 1'".

Because of the declarative model of HTML, and some of its other qualities such as its ability to express positional structure, it's a more natural (and thus simpler) language for defining user interfaces than imperative languages. (It's more natural to express how something looks declaratively - using declarations - than imperatively - using the sequence of operations that made it look as it does. For example, if you were to describe a painting to someone, would you say "Its a portrait of a lady with a faint smile" or "First, the artist drew the face of a lady, then ...".)

But HTML has a shortcoming - its declarative powers are, in a sense, weak. The declarations HTML allows us to make cannot represent state (data that changes over time), they can only represent constants. You cannot represents, for example, the game's score state in a HTML declaration, instead you must settle for "1", "2" or whatever the game score is at a certain time.

So what happens when the game score changes? To the HTML declarations, nothing. Because HTML declarations cannot represent state, the UI will not even know there was a change, therefore the UI will display outdated data.

The traditional solution to this problem was to use JavaScript directly to alter the UI definition in response to state change. JavaScript gives the ability to listen for state change (or the events that cause the state change) and the ability to alter a UI definition. Using these two, we can keep a UI in sync with the state it presents.

In this traditional approach, you would have your initial UI definition in a HTML file to show the initial state:

<main>
  <span id="game-score">Game Score: 0</span>
</main>

And then you would have JavaScript containing within it dynamic fragments of the UI definition used to replace the parts of the UI definition that are outdated by a state change:

const scoreEl = document.getElementById("game-score")
const gameScore = state(0);

gameScore.listen(score => {
  scoreEl.innerText = `Game Score: ${score}`
  //                   ^^^^^^^^^^^^^^^^^^^^
  //                   Fragment of the UI definition
})
  
gameScoreIncrementingEvent.listen(() => {
  gameScore.update(old => old + 1)
})

This meant that the UI definition would span two worlds: the declarative world of HTML and the imperative world of JavaScript. More than that, in more complex cases, the UI definition in the JavaScript would be fragmented and possibly scattered about in the JS code, making it even more complicated to read, modify and maintain the UI definition.

The Modern Approach

Modern UI development toolkits deal with the problem of HTML's declarative weakness by providing more powerful and higher-level declarative languages that can represent state.

<span>Game score: {gameScore}</span>

(I'm using the syntax {...} to symbolize state)

As was mentioned earlier, state is data that changes, therefore, when we say that a language can represent state, we mean that the language takes into account state's dynamic nature - when the state in the language's declarations change, the interpretation of the declarations change along with the state (though the declarations themselves remain the same). For example, when the game score state is 1 then the above declaration can be interpreted as

<span>Game score: 1</span>

And when the game score changes to 2, the same declaration will then be interpreted as

<span>Game score: 2</span>

A UI definition made with these kinds of languages does not need you to use JS directly to update it when state changes, it will, by its nature, (conceptually) stay in sync with the state. We can therefore have an up-to-date UI without scattering our UI definition about in our JavaScript.

<main>
  <span>Game score: {gameScore}</span>
</main>


<script lang="js">
  const gameScore = state(0);
  
  gameScoreIncrementingEvent.listen(() => {
    gameScore.update(old => old + 1)
  })
</script>

Some popular examples of UI toolkits that have languages that can represent state are React, Vue and Angular.