Building a JSX + DOM library Part 2
Tags: javascript, jsx, dom, react
Published: 2019-05-11 21:16:02

---
title: Building a JSX + DOM library Part 2
published: true
description: An article series on making a React-like library.
tags: javascript,jsx,dom,react
---

We have now reached a point where complexity will increase a great deal compared to the [simplicity of the first part](https://dev.to/merri/building-a-jsx-dom-library-part-1-3ga6). This complexity is caused by two things:

1. We want to be React-like in making changes to the DOM tree via single JSX representation.
2. `dom()` must output DOM nodes only

## Setting a target

In the first part we ended up with this application code:

```jsx
function Component(props) {
    function changeColor() {
        render(ref, { style: 'background: red; padding: 5px;' })
    }

    const ref = (
        <div style={props.style}>
            <h1>Hello world!</h1>
            <button onclick={changeColor}>Change color</button>
        </div>
    )

    return ref
}

const App = <Component style="background: gray; padding: 5px;" />

document.body.appendChild(App)
```

We want to get rid of some issues here:

1. There should be no need to capture a local `ref`
2. Our component `props` shouldn't be direct DOM element attributes
3. `changeColor` shouldn't need to know about `render`

In short we want to transition from pure *DOM mutation* into *state mutation* where the developer using the library can focus on what he is doing and not care too much about the library. Or put other way: use components to describe what things should be like instead of manually writing DOM manipulation code.

How could we mangle the JSX so that we could as library authors get something to work with? If we look at React, it renders component render methods all the time. As such we do not have a render method at the moment. We need to add function somewhere. So how about...

```jsx
function Component(props) {
    function changeColor() {
        props.dark = !props.dark
    }

    return (
        <div style={() => `background-color: ${props.dark ? 'red' : 'wheat'}; padding: 5px;`}>
            <h1>Hello world!</h1>
            <button onclick={changeColor}>Change color</button>
        </div>
    )
}

const App = <Component dark={false} />

document.body.appendChild(App)
```

Doesn't this look good? We now have a function in `style` attribute which we can call. We also have local state with the component which we can mutate because it is something we own. And best of all the syntax is quite readable, easy to reason about and there are no signs of library.

This does give challenges and questions: shouldn't we distinguish between functions like `onclick` and `style`? How do we render again after changes to state?

## Dealing with the functions

From now on there is quite a lot of code to work with, so to ease following here is the complete code from part 1:

{% codepen https://codepen.io/Merri/pen/mYEmaJ %}

From here let's adjust the application code to add features step-by-step. Our initial step is to introduce functions!

```jsx
// --- Application ---

function Component(props) {
    function changeColor() {
        props.dark = !props.dark
        render(ref)
    }

    const ref = (
        <div style={() => `background-color: ${props.dark ? 'red' : 'wheat'}; padding: 5px;`}>
            <h1>Hello world!</h1>
            <button onclick={changeColor}>Change color</button>
        </div>
    )

    return ref
}

const App = <Component dark={false} />

document.body.appendChild(App)
```

We got pretty close to what we want! Now the only bad thing is that we have `render` and that we need to manually track `ref`. We'll deal with these issues later on.

As such the application is now "broken", because `style` is clearly not working. We need to start managing our props, our one-liner `Object.assign(element, props)` is no longer fit for our needs.

We have two pieces of code that use this call. This means we need to build a new function that manages this specific task! We shall call this method `updateProps`. Before we write that we can update the calling methods and as we go there is no longer need to pass `nextProps` to render:

```jsx
// --- Library ---

const propsStore = new WeakMap()

function updateProps(element) {
    const props = propsStore.get(element)
}

function render(element) {
    if (!propsStore.has(element)) return
    updateProps(element)
}

function dom(component, props, ...children) {
    props = { ...props }
    const element = typeof component === 'function'
        ? component(props)
        : document.createElement(component)
    propsStore.set(element, props)
    updateProps(element)
    return children.reduce(function(el, child) {
        if (child instanceof Node) el.appendChild(child)
        else el.appendChild(document.createTextNode(String(child)))
        return el
    }, element)
}
```

`updateProps` only needs to take `element` in as we can simply get reference to `props`. There is no reason to do this when calling it.

`render` will be a public method, while `updateProps` is intended to be internal to the library. This is why `render` does a check for existance of the element in the `propsStore`.

It is time to write some logic to handle the functions!

```jsx
function updateProps(element) {
    const props = propsStore.get(element)
    Object.entries(props).forEach(([key, value]) => {
        if (typeof value === 'function') {
            // use event handlers as they are
            if (key.slice(0, 2) === 'on') {
                if (element[key] !== value) element[key] = value
                return
            }
            // call the function: use element as this and props as first parameter
            value = value.call(element, props)
        }
        // naively update value if different
        if (element[key] !== value) {
            element[key] = value
        }
    })
}
```

And now when we run the app we should have a wheat colored background. Do we?

{% codepen https://codepen.io/Merri/pen/EzymJO %}

Success! However... why doesn't the button work? We have to debug. So, good old console logging: `console.log('updateProps', element, props)` before `Object.entries` should show us what is wrong.

And the result:

```js
"<div style='background-color: wheat; padding: 5px;'>...</div>" Object {
  dark: true
}
```

Well darn! We no longer get `style` props here, instead we get the component's props! We do need component's props to pass them as first parameter to the function as that will be useful for currently unrelated reasons, but we also need to distinguish between component and element.

Our line to blame is in `dom` method: there we set `propsStore` without checking if we already have a reference. This gets called twice: first when `dom` creates `div` element and a second time for the same `div` when `Component` is called.

A simple solution to this is to ignore components:

```js
function dom(component, props, ...children) {
    props = { ...props }
    const isFn = typeof component === 'function'
    const element = isFn ? component(props) : document.createElement(component)
    if (!isFn) propsStore.set(element, props)
    updateProps(element)
    return children.reduce(function(el, child) {
        if (child instanceof Node) el.appendChild(child)
        else el.appendChild(document.createTextNode(String(child)))
        return el
    }, element)
}
```

And does our code work?

{% codepen https://codepen.io/Merri/pen/yWJXzR %}

It does! The button now correctly swaps between two colors. This brings us to the end of the second part.

There are further challenges to solve:

1. Component props would be nice to pass to the attribute prop functions.
2. We still need to call `render` manually and keep `ref`.
3. If we move `style` to `h1` element then our click no longer works :(

The first and second are challenging; the third should be an easier one to solve. Can you solve it before the next part is out?

---

Other parts: [1](https://dev.to/merri/building-a-jsx-dom-library-part-1-3ga6), [3](https://dev.to/merri/building-a-jsx-dom-library-part-3-2kgc), [4](https://dev.to/merri/building-a-jsx-dom-library-part-4-ref-in-peace-53nf)