Building a JSX + DOM library Part 3
Tags: javascript, jsx, dom, react
Published: 2019-05-12 18:58:22

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

In the [previous part](https://dev.to/merri/building-a-jsx-dom-library-part-2-kk3) we got us a challenge: update the `h1` component's `style`, too!

The most obvious place to take care of this problem is in `render`. So far we've only taken care of rendering the root element and ignored it's children. Adding a loop that recursively calls render for the remaining child nodes does the magic for us:

```jsx
function render(element) {
    if (!propsStore.has(element)) return
    updateProps(element)
    for (let child of element.childNodes) {
        render(child)
    }
}
```

We use `render` because we aren't guaranteed that the child element is created or managed by our library. Also, calling `render` ensures we also call children of the child.

To make use of this change to the library we also need to update our application code. Using white text color for red background might work nicely!

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

And as a result our `h1` element should now update:

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

Which it does :) In the CodePen sample I've added some `console.log` to `updateProps` that makes it now easier to see all the mutations applied. You can already find some improvements to be made: for example, wheat background color is set twice despite no effective changes. For the moment we let that be (you can do otherwise, of course!).

---

A good reason for ignoring optimization now is that we don't have a complete feature set yet. The more code we have the harder it becomes to implement new features. And optimizations tend to be tricky on their own: it would make sense to have tests before going all-in with optimizations.

At this point we're still in the early phase of adding in all the basic features that we need to have a "complete" usable React-like library.

---

So, where should we go next? It does itch a lot to go ahead and remove the final annoyance of `render(ref)` and seemingly be "feature complete" with our current application code where it could truly be an independent component with minimal boilerplate required by the application side developer.

But there is actually an issue with components at the moment. We can reveal this when we abstract `h1` to it's own component:

```jsx
function HelloWorld(props) {
    return (
        <h1 style={() => `color: ${props.dark ? 'white' : '#333'};`}>
            Hello world!
        </h1>
    )
}

// and in Component replace h1 with...
<HelloWorld dark={() => props.dark} />
```

Our text is always white! Why? If we debug `props.dark` inside `HelloWorld`, we notice one thing: is is a function. This means it gets passed through untouched instead of being managed. And we must pass it as a function to the component in order to be able to update `dark` value. It won't ever get updated if we don't use a function to help us out due to the limitation of what we have.

## Managing components

Our component abstraction is clearly not up to the task. When we look into `dom` we notice that we omit all props management of components: `if (!isFn) propsStore.set(element, props)`. Also, all our current rendering code assumes only native DOM nodes.

We also still have a feature we'd like to have: passing component's props as input to the attribute functions. One reason we like this is that it would allow optimization of those functions (such as memoize), which would be great in cases where execution of the function is costly.

We have a few requirements in order to manage components:

1. Something needs to link elements and their related components with.
2. We need to store component props somewhere so that we can pass them.

For the first thing we can't use the component's function as a reference because we might use the same component multiple times. To ease figuring out this problem we could take a step back. What does `dom` need to output? A valid DOM node. Is there something we could use that can wrap other DOM nodes?

**Fragments!** Fragments are special DOM nodes in that they only ever exist at the top of the tree. Fragments can't exist as child nodes: their child nodes are always automatically added instead, and removed from the fragment.

The second point is now easier to answer: we can use the existing `propsStore` and use a fragment as our reference. We can now go ahead and start implementing a code that marks elements to belong into a component so that we can then give component's props as input for the attribute functions of those elements.

Huh. That is some complexity! We are now going to go through a lot of changes to the existing library methods, and have a couple of new internal helper functions to look at.

## Changes to `dom`

From here on I'm switching from Codepen to Codesandbox as the amount of code is starting to exceed one file. The library part of the code will reign on `library.js` and will `export` two methods: `dom` and `render`.

Before going through the methods, we've added two new WeakMaps:

```js
const componentPropsStore = new WeakMap()
const parentComponents = new WeakMap()
```

Now let's go ahead and see what new we have.

```js
export function dom(component, props, ...children) {
    props = { ...props }
    const isComponent = typeof component === 'function'
    // create the output DOM element
    const element = isComponent
        ? document.createDocumentFragment()
        : document.createElement(component)
    if (isComponent) {
        // remember original props
        componentPropsStore.set(element, props)
        // create new object that gets the updates of function calls
        const exposedProps = updateComponentProps({}, props)
        // store like normal element props
        propsStore.set(element, exposedProps)
        // call component to create it's output
        element.appendChild(component(exposedProps))
        // mark each DOM node created by us to this component
        for (let child of element.childNodes) {
            setParentComponent(child, element, exposedProps)
        }
    } else {
        propsStore.set(element, props)
        updateProps(element)
    }
    // untouched here, so we're gonna have problems at some point :)
    return children.reduce(function(el, child) {
        if (child instanceof Node) el.appendChild(child)
        else el.appendChild(document.createTextNode(String(child)))
        return el
    }, element)
}
```

One function and we already have two new functions introduced!

1. `updateComponentProps` manages calling functions and updating the resulting state, which is then exposed to the component
2. `setParentComponent` marks all children of the called component to that component, including another components

But we're not yet ready going through changes to the existing methods.

## Changes to `render`

```js
export function render(element, fragment, componentProps) {
    if (!propsStore.has(element)) return
    // detect parent component so that we can notice if context changes
    const parent = parentComponents.get(element)
    if (parent !== fragment) {
        // the context changed
        fragment = parent
        // update component props by calling functions
        const props = componentPropsStore.get(fragment)
        if (props) {
            componentProps = updateComponentProps(
                propsStore.get(fragment),
                props,
                componentProps
            )
        }
    }
    // we now pass the relevant componentProps here!
    updateProps(element, componentProps)
    for (let child of element.childNodes) {
        render(child, fragment, componentProps)
    }
}
```

Here we update component props upon render. Instead of creating the props again and again we do the work only when the component changes.

## Changes to `updateProps`

The least changes have happened here.

```js
function updateProps(element, componentProps) {
    const props = propsStore.get(element)
    Object.entries(props).forEach(([key, value]) => {
        if (typeof value === 'function') {
            if (key.slice(0, 2) === 'on') {
                if (element[key] !== value) {
                    element[key] = value
                }
                return
            }
            // no component props known, no game!
            if (!componentProps) return
            value = value.call(element, componentProps)
        } else if (componentProps) {
            // this is an optimization that reduces work
            // but: maybe it introduces bugs later on!
            return
        }
        if (element[key] !== value) {
            element[key] = value
        }
    })
}
```

For the most part we're simply passing through the props that interest us.

## The new methods

We have two new methods and here are both:

```js
function setParentComponent(element, fragment, componentProps) {
    // already marked to someone else?
    if (parentComponents.has(element)) {
        // check if the parent component of this element has a parent
        const parent = parentComponents.get(element)
        if (!parentComponents.has(parent))
            parentComponents.set(parent, fragment)
        return
    }
    // are we tracking this element?
    if (!propsStore.has(element)) return
    // mark parent and manage props, then continue to children
    parentComponents.set(element, fragment)
    updateProps(element, componentProps)
    for (let child of element.childNodes) {
        setParentComponent(child, fragment, componentProps)
    }
}

function updateComponentProps(componentProps, props, parentProps = {}) {
    return Object.entries(props).reduce((componentProps, [key, value]) => {
        if (typeof value === 'function' && key.slice(0, 2) !== 'on') {
            componentProps[key] = value(parentProps)
        }
        return componentProps
    }, componentProps)
}
```

And that is the final piece of the puzzle completed. Summary of what have been achieved:

1. Components are rendered as fragments
2. Components now know each of their children, including other components
3. We can pass component's props to their child functions
4. Components can update as their props change

The library has now gained a lot of functionality while still being less than 100 lines of total code! Let's have a look at a working application:

{% codesandbox n02o1r2mql %}

---

Time for some reflection. I know that this article series isn't teaching in a convenient step-by-step manner: I'm not getting too much stuck on details and instead steamroll with working code. However, I hope the contents so far have given some insight into how an experienced developer approaches things and how building an idea into a fully working library comes together. Feel free to throw questions, feedback and critique in the comments!

In the next part it is time to manage the last piece of annoyance in the current application side code: getting rid of `render` and `ref`!

---

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