LBAVDL 06: Handling state and events

by wildlyinaccurate

HTML

<div id="app"></div>
<script>
window.addEventListener("load", () => {
    class Counter extends Component {
        state = { count: 0 };

        constructor(props) {
            super(props);
        }

        increment() {
            this.setState({ count: this.state.count + 1 });
        }

        render() {
            const buttonText = this.props.buttonText || "+1";

            return createVNode("div", null, [
                `Counter value: ${this.state.count} `,
                createVNode("button", { onclick: () => this.increment() }, [
                    buttonText,
                ]),
            ]);
        }
    }

    function app() {
        return createVNode("div", { className: "container" }, [
            createVNode(Counter),
            createVNode(Counter, { buttonText: "Add One" }),
        ]);
    }

    mount(app(), document.getElementById("app"));
});
</script>

JavaScript

function createVNode(type, props, children) {
    return {
        type,
        props,
        children,
        _dom: null,
    };
}

// The mount function takes a vnode and mounts it to an existing "parent" DOM node
function mount(vnode, parentDom) {
    let domNode;

    if (typeof vnode === "string" || typeof vnode === "number") {
        // When the node is a string or number, we can insert a plain text node.
        domNode = document.createTextNode(vnode);
    } else {
        if (typeof vnode.type === "function") {
            // If the vnode type is a function, we can assume it is a component.
            domNode = createComponentNode(vnode, parentDom);
        } else {
            // For "regular" vnodes, we create a HTMLElement of the node's type.
            domNode = document.createElement(vnode.type);
        }

        // Store the DOM node on the VDOM node for future updates.
        vnode._dom = domNode;

        if (vnode.props) {
            // All of the props are set as attributes on the HTMLElement.
            for (const prop in vnode.props) {
                domNode[prop] = vnode.props[prop];
            }
        }

        if (vnode.children) {
            // Any children go through the same process recursively until we have
            // mounted the whole tree.
            vnode.children.forEach((child) => mount(child, domNode));
        }
    }

    // When we're finished, append the new DOM node to the parent.
    parentDom.appendChild(domNode);

    return domNode;
}

// Create a component node for a component vnode
function createComponentNode(vnode, parentDom) {
    const Component = vnode.type;

    // Create an instance of the component with the props passed in, and call
    // the component's render() function to get its VDOM tree.
    const instance = new Component(vnode.props);
    const newVNode = instance.render();

    // Store the component's parent DOM and VDOM tree on the instance for
    // future updates.
   ...