updateState is the component method for changing internal state without losing existing fields. It merges the supplied state into the current state, then triggers rendering and DOM patching so the view reflects the change. section 9.1.1 section 9.3.2
The mechanism has three small responsibilities:
- Merge partial state with the spread expression: this.state = { ...this.state, ...state }. Unmentioned state properties remain available. section 9.3.1
- Call the private patch routine after updating state. section 9.3.1
- Have patch render a new virtual DOM, compare it with the previous virtual DOM, and apply the result through patchDOM(). section 9.3.1
- Reject patching when the component is not mounted; updateState therefore operates on a mounted component. section 9.3.1
Listing 9.4 Update the state and the component’s DOM (component.js)
import { destroyDOM } from './destroy-dom'
import { mountDOM } from './mount-dom'
import { patchDOM } from './patch-dom'
export function defineComponent({ render, state }) {
class Component {
#isMounted = false
#vdom = null
#hostEl = null
constructor(props = {}) {
this.props = props
this.state = state ? state(props) : {}
}
updateState(state) {
this.state = { ...this.state, ...state } #1
this.#patch() #2
}
render() {
return render()
}
mount(hostEl, index = null) {
// --snip-- //
}
unmount() {
// --snip-- //
}
#patch() {
if (!this.#isMounted) { #3
throw new Error('Component is not mounted')
}
const vdom = this.render() #4
this.#vdom = patchDOM(this.#vdom, vdom, this.#hostEl) #5
}
}
return Component
}
The implementation listing shows the complete update path: merge state, invoke #patch(), render, and patch the component DOM. section 9.3.1
Incrementing a counter
A counter can call updateState from a button handler: updateState({ count: this.state.count + 1 }). The render function reads this.state.count, so the next virtual DOM contains the incremented label. The render callback must be bound with render.call(this); otherwise this can be undefined and reading count fails. section 9.3.1
