DEV Community

Cover image for What is Reactivity?
Corbin Crutchley for This is Learning

Posted on • Originally published at unicorn-utterances.com

What is Reactivity?

This article is intended for newcomers to HTML and JavaScript programming. However, it's suggested that you read this article explaining what the DOM is first.

As an experienced frontend engineer, I'm often asked:

"Why would you want to use a modern frontend framework like React, Angular, or Vue?"

While I have a whole (free) book on the topic, my short answer is typically "Reactivity". The follow-up response I usually get from this is:

"What is reactivity?"

In short, Reactivity is the ability to reflect what's in your JavaScript application's memory on the DOM as HTML.

See, when you're building a website using only static HTML, the output to the DOM is straightforward.

<!-- index.html -->
<main id="a">
    <ul id="b">
        <li id="c">Item 1</li>
        <li id="d">Item 2</li>
    </ul>
    <p id="e">Text here</p>
</main>
Enter fullscreen mode Exit fullscreen mode

A chart showing the document object model layout of the above code. It shows that the 'main' tag is the parent to a 'ul' tag, and so on

The problems start when we want to introduce interactivity into our output.

Let's build a small-scale application that:

  • Has a button with a counter inside of it
  • Start the counter at 0
  • Every time the button is clicked, add one to the counter

A 'main' tag that has a button child. This button updates a JavaScript count when pressed and when that value is updated, will change the text of the button

To do this, let's start with some HTML:

<main>
  <button id="add-button">Count: 0</button>
</main>
Enter fullscreen mode Exit fullscreen mode

Then we can add in the required JavaScript to make the button functional:

<script>
  let count = 0;

  const addBtn = document.querySelector('#add-button');
  addBtn.addEventListener('click', () => {
    count++;
    addBtn.innerText = `Count: ${count}`;
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Adding a List

Not too bad, let's increase the difficulty a bit by:

  • Adding an unordered list (<ul>)
  • Every time count is increased, add a new <li> with a unique string inside

The main has a button and an unordered list. This button updates the JavaScript count value, which in turns updates the button text and adds a list to the ul element

That might look something like this:

<main>
  <button id="add-button">Count: 0</button>
  <ul id="list"></ul>
</main>
<script>
  let count = 0;

  const listEl = document.querySelector('#list');

  function makeListItem(innerText) {
    const li = document.createElement('li');
    li.innerText = innerText;
    listEl.append(li);
  }

  const addBtn = document.querySelector('#add-button');
  addBtn.addEventListener('click', () => {
    count++;
    addBtn.innerText = `Count: ${count}`;
    makeListItem(`List item: ${count}`);
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Removing items from the list

Okay! Things are heating up! For one last exercise, let's:

  • Add a button that removes 1 from count
  • When this button is pressed, remove the last element from the list

There are two buttons now, each change the JavaScript count value. This count value is then reflected in the text nodes for each button, and the list respectively

Notice how complex our logic tree is getting?

<main>
  <button id="add-button">Add one to: 0</button>
  <button id="remove-button">Remove one from: 0</button>
  <ul id="list"></ul>
</main>
<script>
  let count = 0;

  const listEl = document.querySelector('#list');

  function makeListItem(innerText) {
    const li = document.createElement('li');
    li.innerText = innerText;
    listEl.append(li);
  }

  function removeListItem() {
    listEl.lastChild.remove();
  }

  const addBtn = document.querySelector('#add-button');
  const removeBtn = document.querySelector('#remove-button');

  function updateBtnTexts() {
    addBtn.innerText = `Add one to: ${count}`;
    removeBtn.innerText = `Remove one from: ${count}`;
  }

  addBtn.addEventListener('click', () => {
    count++;
    updateBtnTexts();
    makeListItem(`List item: ${count}`);
  });

  removeBtn.addEventListener('click', () => {
    count--;
    updateBtnTexts();
    removeListItem();
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Wow! That got complex, quick, didn't it?!

Exactly... That leads me to the question:

Shouldn't it be simpler?

Notice how each time we added another item that depended on count, our data didn't change. Instead, we had to add ever increasing levels of complexity to our codebase to glue our JavaScript state to the DOM representation of said state.

If we strip away all of this glue, we're left with a drastically simplified codebase:

<main>
  <button id="add-button">Add one to: 0</button>
  <button id="remove-button">Remove one from: 0</button>
  <ul id="list"></ul>
</main>
<script>
  // Magical land where `count` changes auto-update the DOM
  let count = 0;

  addBtn.addEventListener('click', () => {
    count++;
  });

  removeBtn.addEventListener('click', () => {
    count--;
  });
</script>
Enter fullscreen mode Exit fullscreen mode

The two buttons update the count, but that's all there is. Everything else is handled invisibly to you

Look at how many lines disappeared!

Not only is this nicer method of writing code theoretically possible, it's widely adopted by millions of developers via a frontend framework.

Some examples of frontend frameworks include:

These frameworks allow you to write code that focused on the data in JavaScript, rather than how it will be bound to the DOM:

React

const App = () => {
    const [count, setCount] = useState(0);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Add one to: {count}</button>
            <button onClick={() => setCount(count - 1)}>
                Remove one from: {count}
            </button>
            <ul>
                {Array.from({ length: count }).map((_, i) => (
                    <li>List item {i}</li>
                ))}
            </ul>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

Angular

@Component({
    selector: "app-root",
    standalone: true,
    imports: [NgFor],
    template: `
        <button (click)="count = count + 1">Add one to: {{ count }}</button>
        <button (click)="count = count - 1">Remove one from: {{ count }}</button>
        <ul>
            <li *ngFor="let item of [].constructor(count); let i = index">
                List item {{ i }}
            </li>
        </ul>
    `,
})
export class AppComponent {
    count = 0;
}
Enter fullscreen mode Exit fullscreen mode

Vue

<script setup>
import { ref } from "vue";

const count = ref(0);
</script>

<template>
    <button @click="count++">Add one to: {{ count }}</button>
    <button @click="count--">Remove one from: {{ count }}</button>
    <ul id="list">
        <li v-for="(_, i) of [].constructor(count)">List item {{ i }}</li>
    </ul>
</template>
Enter fullscreen mode Exit fullscreen mode

This, dear reader, is the core idea behind reactivity: Allowing us to focus on how we want to change the state stored in JavaScript and allowing some other mechanism to abstract away how it shows up on-screen.

These mechanisms can have wildly different methods to them, too!

For example, here's what each of the frameworks utilize under-the-hood:

Framework Reactivity Method Rendering Method
React Explicit Function Calls VDOM
Angular Zone.js Incremental DOM
Vue Proxies VDOM

This is real nerd hours, don't feel bad if this just looks like gibberish to you right now.

Conclusion

This has been a look at what reactivity is and why you might want to use a modern frontend framework to utilize it in your apps today.

Next time, we'll talk about what "Reconciliation" is and how it impacts most React and Vue frontend applications today.

Want a place to ask questions as you're learning on your journey? Join our Discord and let us know what you thought of this article. We'd love to get to know you!

Top comments (6)

Collapse
 
efpage profile image
Eckehard

Well explained. Thank you!

Though these frameworks are quite poplar, they have their own challenges. There is simply no sun without shadow!

An event based approach can be a bit more demanding, but it is possible to use it in a much more organized way than you explained. This is your example rewritten in DML:

let count = 0
function update(){
  count = list.children.length
  b1.textContent  = `Add one to: ${ count }` 
  b2.textContent  = `Add one to: ${ count }` 
}

// Build the DOM 
let b1=button()
let b2=button(); br()
let list = ul()
update()

// add Reactivity
b1.onclick = () => { list.add([`List item: ${count+1}`]); update() }
b2.onclick = () => { list.lastChild.remove(); update ()}
Enter fullscreen mode Exit fullscreen mode

A working example is here

Collapse
 
crutchcorn profile image
Corbin Crutchley

There will always be tradeoffs with any approach. Event-based reactivity is still reactivity, however ;)

Collapse
 
efpage profile image
Eckehard

I was thinking if it was possible and useful to mix both approaches. You do this all the time with React, as all DOM elements are stateful, so they preserve their own state and React cares for the state transitions.

We do the same with webcomponents, that also have their own stat logic that is not coupled to any framework and they handle any internal changes very well. Think of a component that changes the background color depending on a given value. Externally, the value can be changed, but the color change is handled internally.

I would call this kind of reactivity "short range reactivity", while more global states that require page changes or manipulation of elements that are not closely coupled the "long range reactivity".

Using events for the "short range logic" seems a good approach to take some load from the state logic. What do you think about this?

Thread Thread
 
crutchcorn profile image
Corbin Crutchley

Not only possible, it's regularly done! Especially with Angular and RxJS. I even wrote a library to use RxJS with React:

crutchcorn.github.io/rxjs-use-hooks/

I'm not sure about the terminology, however. I know there are very specific terms that already exist for this kind of thing but I don't often engage very heavy in it. So long as there's a conceptual understanding I'm okay with having semi-ambigious terminology usage from time-to-time

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Amazing post 🥰. Love the simplicity of the explanations and the nice illustrations.
This is the reason why I love React and struggle to do anything with vanilla JS 🙈

Collapse
 
crutchcorn profile image
Corbin Crutchley

Thanks so much for the kind words 😊