Front End Tip: Watch Data not DOM

It will always be good to keep data as a single source of truth and make the DOM a reflection of it. This is what all major libraries are doing as well.

Front End Tip: Watch Data not DOM

Watch Data not DOM
This important tip is “Watch Data not DOM”. It will always be good to keep data as a single source of truth and make the DOM a reflection of it. This is what all major libraries are doing as well.

This post is from the answer I wrote on Quora which can be read here: https://www.quora.com/JavaScript-fails-in-computing-arrays-when-user-interaction-is-very-fast-could-it-just-be-me/answer/Pankaj-Patel?srid=31Gd.

Obviously you will need to implement the supplementary data structure to support the program but that will be worth it.

In JavaScript, Reading and Writing/Updating the DOM are costly things, and it has side effects and quirks of its own.

Consider this example:

<p id="sum">0</p>
<div id="items">
  <p><input type="checkbox" class="item item-1" value="5" />5</p>
  <p><input type="checkbox" class="item item-2" value="14" />14</p>
  <p><input type="checkbox" class="item item-3" value="3" />3</p>
  <p><input type="checkbox" class="item item-4" value="8" />8</p>
</div>
<script>
items = Array.prototype.slice.call(document.querySelectorAll('.item'))
function add() {
  let sum = 0
  items.forEach(item => {
    if(item.checked) {
      sum += parseInt(item.value);
    }
  })
  document.querySelector('#sum').innerHTML = sum;
}
items.forEach((item) => {
  item.addEventListener('click', add);
});
</script>

Now this example will run add function everything something is checked or unchecked. and in case large number of checkboxes; it will be slow and won’t generate correct results in case of large data as program will have to go through each and every DOM element to identify the data and make sure what to do with it.

We can redo this example as following:

<p id="sum">0</p>
<div id="items"></div>
<script>
let items = [5, 14, 3, 8];
let sum = {
  total: 0,
  items: {}
}
items.forEach((item, index) => {
  sum.items[index] = {checked: false, value: item};
  document.querySelector('#items').innerHTML += '<p>\
    <input type="checkbox" class="item"\
     data-index="'+index+'" value="'+item+'"\
     /> '+item+'</p>';
});
 
document
.querySelector('#items')
.addEventListener('click', (event) => {
  let item = event.target;
  if( item.classList.contains('item') ) {
    if(sum.items[item.dataset.index].checked) { //already checked
    sum.total -= parseInt(item.value);
    } else {
    sum.total += parseInt(item.value);
    }
    sum.items[item.dataset.index].checked = item.checked;
  }
  document.querySelector('#sum').innerHTML = sum.total;
})
</script>

The above code has one event handler and has track of everything through data.

Lookup in data is easy rather than in DOM.

Moreover when you are making sure that DOM is in sync with Data, events are also easy to handle. Like in above example, all the events are delegated to the container and event itself has all the necessary information about the actions. event.target is the reference to the element which has be acted itself, it is does not require another query to DOM again. Event handlers are also reduced from being equal to the number of checkboxes in the app to just one handler.

So before approaching the problems of UI, just sit back and think about the data. Once the necessary transitions are clear, you can go ahead and write the code in a very declarative and functional manner; rather pure functions.