Reactivity in a framework is a declarative programming model that takes care of keeping the DOM(Document Object Model) in sync with the updates to current state

I know that it's hard to sip, let's get practical so that we solidify our mental models and get a good hold of it!

Let's code up a plain old counter by hand. With the advent of many javascript frameworks and libraries, it is a pretty easy task to accomplish. Will it be the same when developed with plain javascript?

Forget all the frameworks and libraries, Your only tool is javascript now and just get ready for the adventure!

Adventure!

index.html:

Index HTML

Our counter will be rendered into # app .

index.js:

class Counter {

  count = 0;

  handleIncrement = () => {
    this.count++;
  };

  handleDecrement = () => {
    this.count--;
  };

}

I'm defining a class Counter , with a property count that defaults to 0 and two methods handleIncrement , handleDecrement that handles increment and decrement actions respectively. Our current state is the count property. Whenever the state is updated, our DOM should be synced up. It shouldn't be stale.

Since we are dealing with plain JS, we should be creating our increment and decrement buttons by hand right? That's what our next task is!

index.js:

  setUpButton(action) {
    const actionHash = {
      Increment: this.handleIncrement,
      Decrement: this.handleDecrement
    };
    const button = document.createElement("BUTTON");
    button.textContent = action;
    button.onclick = actionHash[action];
    return button;
  }

Our setupButton method ensures that It creates a button and associates the respective onclick handler according to the action passed as an argument. So we are done with the functionality. Not bad till now. Let's get it into DOM . We should be coding up our render method now!

index.js:

  render() {

    const app = document.getElementById("app");
    app.innerHTML = "";
    const count = document.createElement("DIV");
    count.textContent = this.count;
    const elementsToAppend = [
      count,
      this.setUpButton("Increment"),
      this.setUpButton("Decrement")
    ];
    const fragment = document.createDocumentFragment();
    elementsToAppend.forEach(element => {
      fragment.append(element);
    });
    app.appendChild(fragment);

  }

This is more of a straight-forward implementation of render method. DOM should be kept in sync with our state count . So we are clearing up any stale elements that were previously rendered at first by setting innerHTML to an empty string . We are creating a div element that renders our count value. Then we set up both our increment and decrement buttons and finally we append everything to the # app element.

Hurray! we are done soon. Let's check whether it's working.

index.js:

new Counter().render();


***Output*** 🤯 ResultOOPS!

Oops, it didn't work as expected 😱

While checking our code we can find that, once we update our state we've failed to render our app again! That's the cause. Let's fix it 🛠

index.js:

  handleIncrement = () => {
    this.count++;
    this.render();
  };
  handleDecrement = () => {
    this.count--;
    this.render();
  };

Finally 😅

Result

The complete source code can be found here.

OMG! see how imperative our solution is 😓. What if we include a magic layer that takes care of these nitty-gritty things. That is, whenever our current state updates, our app should magically rerender declaratively. That's the way to go, right? What if we add another state in the future and failed to do the same? This solution is less maintainable and not future proof.

To the surprise, the modern javascript frameworks and libraries actually act as the magic layer underhood that takes care of these low-level tasks and makes you more productive by letting you concentrate entirely on the app business logic. The DOM will be in-sync with the state updates and that's a promise given by modern frameworks and libraries. And also we can't simply rerender the whole app for a single state change. These tools also ensure that they efficiently update the DOM and only re-render the parts that are only necessary.

These tools have their own ways of handling state management.

How does React handle it?

React achieves state tracking via the useState API in functional components.

React

With useState , now the solution is more maintainable and readable and less error-prone. Future updates can be done seamlessly.

useState function imported from react when invoked, returns an array. It contains two elements, the first one denotes the state variable itself, while the second element references a function that can be invoked to update that particular state variable. You can't simply use this.count++ or this.count-- directly as we do in plain JS. We should only use the respective state updater functions. This solution is more declarative than the previous one that we hand-coded with plain JS.

But what if I say that there is a more elegant way for achieving this?

OMG!

Ember, a framework for ambitious web applications provides us some great APIs that are more natural-looking and syntactically very declarative. You can be free from using any state updater functions like this.setState() . Just count++ or count-- is enough. This is how we do in javascript right?

Octane edition is the latest update in Ember . This has amazed me with lots of cool new features and a more organized declarative programming model. If I had to pick one out of them, the new Reactivity model earns the medal, to be honest. Let's see how our counter can be implemented with Ember 🤗

Counter.js:

Ember

Counter.hbs:

HBS

I personally feel this approach to be more natural. You just tell Ember which properties you wanna keep in the state. Ember automatically tracks that particular property and keeps the DOM in-sync on updates to it. Also, your markup is now split into a separate handlebars file, so that your business logic now becomes less clunky and more readable 🤩

This is a lot for now. Let me know your thoughts regarding our approach in the comments below.

Interested to know more about how @tracked imported from @glimmer/tracking achieves this complex work underhood?

Want to know how @tracked keeps the track of different state properties and triggers rerender based on the updates on them?

Curious to know about their internals?

This is what exactly I'll be covering up in my next post. Can't wait for excitement! Meet you there again folks, bye! 🤟🏻

This post is also available on DEV.