🛠️ Tiny: Updating state

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Tiny: Updating state

State is usually a term given to a component’s value that changes when an event happens. In this case, both the parent’s count and child count are considered to be states.

We can create a state property to hold any initial state values.

Tiny({
  // ...
  state: {
    count: 10,
    childCount: 5
  }
})

We can then render these values by using this in template. It works because this points back to the component which contains the state.

Tiny({
  // ...
  template () {
    console.log(this)
  }
})

This is how we render the parent’s initial count.

Tiny({
  // ...
  template () {
    return `
      <div class="component parent-component flow">
        <h1>Parent Counter</h1>
        <p>Count: ${this.state.count}</p>
        <button tiny-listener="[click, increaseCount]">Increase count by 1</button>
		    <!-- ... -->
      </div>
    `
  }
})

And this is how we render the child’s initial count.

<!-- Child's counter -->
<h2>Child Counter</h2>
<p>Count: ${this.state.childCount}</p>

<!-- Total counter -->
<h2>Total Count</h2>
<ul>
  <li>Parent Count: ${this.state.count}</li>
  <li>Child Count: ${this.state.childCount}</li>
  <li>Total Count: ${this.state.count + this.state.childCount}</li>
</ul>

Accessing State

Event handlers need to be able to access state. Unfortunately, they cannot access state right now because this points back to the listening element.

This is correct because this points back to the listener element if you use a normal callback function. We talked about it in the lesson on this.

If we want to access this inside each event handler, we need to change the value of this with bind. In this case, we bind options as this because options is essentially the component itself.

export default function Tiny (options) {
  // ...
  function _addEventListeners () {
      // ...
      listenerElement.addEventListener(eventName, options[fn].bind(options))
    }
  }
}

Once we do this we can access state with this inside the event listener. We can also access other methods and properties.

Changing the state

We can change the state object directly inside the event listener, but nothing will change in the DOM.

Tiny ({
  // ...
  increaseCount (event) {
    this.state.count = this.state.count + 1
  }
  // ...
})

That’s because changing the state object is not enough. We did not render the new state back into the DOM.

To render the new state back into the DOM, we need to call the _render function. But Tiny users cannot use this function since we did not expose it.

This means we need to give users a way to tell Tiny that the state has changed. And when the state changes, Tiny should re-render the DOM.

React calls this method setState. Let’s call ours setState too.

export default function Tiny (options) {
  // ...
  function setState () {
    console.log('Setting state')
  }
}

We need to give Tiny users access to setState. One way to give this access is to declare setState as a method of options.

export default function Tiny (options) {
  // ...
  options.setState = function () {
    console.log('Setting state')
  }
}
Tiny({
  // ...
  increaseCount (event) {
    this.setState()
  },
  // ...
})

We can change the state values inside setState and call the _render function again. To prove this point, let’s set count to 100 in setState.

export default function Tiny (options) {
  // ...
  options.setState = () {
    options.state.count = 100
    _render()
  }
}

But we don’t want to fix the count. We want to let users set the state themselves.

One way to do this is to let them pass in an object that contains the values they want to set. We’ll then loop through every property in this object and assign it back into state.

After assigning the properties back into state, we call _render so the component gets re-rendered.

export default function Tiny (options) {
  // ...
  options.setState = function (newState) {
    const entries = Object.entries(newState)
    for (const entry of entries) {
      options.state[entry[0]] = entry[1]
    }
    _render()
  }
  // ...
}

We can now pass in the new count value into setState.

Tiny({
  // ...
  increaseCount (event) {
    this.setState({
      count: this.state.count + 1
    })
  }
  // ...
})

This works, but the DOM only updates one time. Why? It happened because we didn’t simply change the textContent of the values that need to be changed. We re-rendered the entire component, so event listeners are removed.

To fix this problem, we need to add event listeners after we call _render.

export default function Tiny (options) {
  // ...
  options.setState = function (newState) {
    const entries = Object.entries(newState)
    for (const entry of entries) {
      options.state[entry[0]] = entry[1]
    }
    _render()
    _addEventListeners()
  }
  // ...
}

DOM Diffing

Usable frameworks like React and Vue don’t re-render the entire component. It takes too much time from a performance standpoint.

Instead, they use a process called DOM Diffing which checks the differences in the DOM so they change only things that need to be changed.

There are two kinds of DOM Diffing:

  • Actual DOM Diffing — where you compare your new HTML with the actual HTML in the DOM.
  • Virtual DOM Diffing — where you compare your new HTML with the current HTML that was saved in JavaScript.

It can be challenging to implement DOM Diffing so we skipped it and chose to re-render the entire component instead. This choice creates performance problems so I don’t recommend using Tiny as a real framework in your projects. But it serves enough to understand what goes behind the scenes when you use a framework.

Finishing up

Let’s finish up the counter component by increasing childCount when the appropriate button is pressed.

Tiny ({
  // ...
  increaseChildCount (event) {
    this.setState({
      childCount: this.state.childCount + 1
    })
  }
  // ...
})