Make sure you've read part 1 before tackling this one. It's really important that it's clear to you how the virtual DOM data structure looks like.

Want to edit the code live and follow along with video ? Checkout my Scrimba recording!

Sketching up react-dom file

Open up react-dom.js file and you should have the following contents:

export const render = (virtualDOM, node) => {};

The goal of the render is to alter the contents of the node DOM container and make sure it accurately reflects the virtualDOM description.

Let's see how we would alter the contents of the node:

export const render = (virtualDOM, node) => {
node.innerHTML = "Hello world";
};

Awesome! It's just we haven't used virtualDOM yet :)

As a reminder this is how the virtualDOM looks like so far:

const element = {
type: "div",
props: { id: "abc", class: "awesome" },
children: [
{
// This is the secondDiv object from above
type: "div",
props: { id: "column-1", class: "column" },
children: ["Hi"],
},
],
};

Create REAL DOM elements from virtual DOM

Let's define, in react-dom.js file an internal, private function, for turning a virtual DOM element into a DOM node.

Let's call it createDOMElement.

It will accept a JSON object that looks like:

{ type: 'div', props: { .... }, children: [... ]}

It will output 1(one) single a real DOM node.

So this is what we'll currently have in react-dom.js file.

const createDOMElement = (virtualDOMElement) => {};

export const render = (virtualDOM, node) => {
node.appendChild(createDOMElement(virtualDOM));
};

As you can see render just defers the creation of the real DOM from virtual DOM to the createDOMElement method.

Then it takes the real DOM node returned by createDOMElement and appends it to the container as a child.

Don't worry about the implementation details of createDOMElement , we'll begin implementing it very soon!

Implementing createDOMElement

For brevity all the code examples in the section refer to the react-dom.js file and more specifically the createDOMElement function.
I'll be omitting for now the render function from the code listing.

The first thing we need to be able is to be able to create a tag node programatically.

Creating an element for a certain tag

It turns out there is a handy DOM method for creating elements programatically, in the form of document.createElement:

const createDOMElement = (virtualDOMElement) => {
const element = document.createElement(virtualDOMElement.type);

return element;
};

Setting tag attributes

Surprinsingly, there is another DOM API for that element.setAttribute(name, value):

const createDOMElement = (virtualDOMElement) => {
const element = document.createElement(virtualDOMElement.type);
Object.keys(virtualDOMElement.props).forEach((attrName) => {
element.setAttribute(attrName, virtualDOMElement.props[attrName]);
});

return element;
};

We are just iterating over the keys in virtualDOMElement.props. The jey names is the name of the DOM element attribute while the value is... the value of that attribute.

Setting up children

The plan is simple: for each child in the virtual DOM children create a real DOM child and append it to the current element.

This is how it looks like:

const createDOMElement = (virtualDOMElement) => {
const element = document.createElement(virtualDOMElement.type);
Object.keys(virtualDOMElement.props).forEach((attrName) => {
element.setAttribute(attrName, virtualDOMElement.props[attrName]);
});
virtualDOMElement.children.forEach((virtualDOMElem) => {
element.appendChild(createDOMElement(virtualDOMElem));
});
return element;
};

Handling text nodes

The above code is fine but it has a glitch. Given our initial virtual DOM:

const virtualDOM = {
type: "div",
props: { id: "abc", class: "awesome" },
children: [
{
type: "div",
props: { id: "abc", class: "awesome" },
children: ["Hi"],
},
],
};

Notice the part children: ["Hi"].

Trying to call createDOMElement("Hi") isn't going to end up well.

And we get there because of the way we are building the child nodes:

virtualDOMElement.children.forEach((virtualDOMElem) => {
element.appendChild(createDOMElement(virtualDOMElem));
});

When one of the children is a string, like in the case above, we need to properly handle that.

The DOM API offers us a way to create text nodes by using document.createTextNode(string).

This is the revised code:

const createDOMElement = (virtualDOMElement) => {
if (typeof virtualDOMElement === "string") {
return document.createTextNode(virtualDOMElement);
}
const element = document.createElement(virtualDOMElement.type);
Object.keys(virtualDOMElement.props).forEach((attrName) => {
element.setAttribute(attrName, virtualDOMElement.props[attrName]);
});
virtualDOMElement.children.forEach((virtualDOMElem) => {
element.appendChild(createDOMElement(virtualDOMElem));
});
return element;
};

So if virtualDOMElement is a string we just call the document.createTextNode API and return the created node.

Otherwise, if it's a child tag, we follow the normal flow and recursively create the tag, set the attributes and process the children of the node.

Putting it all together

In react-dom.js you should have the following:

const createDOMElement = (virtualDOMElement) => {
if (typeof virtualDOMElement === "string") {
return document.createTextNode(virtualDOMElement);
}
const element = document.createElement(virtualDOMElement.type);
Object.keys(virtualDOMElement.props).forEach((attrName) => {
element.setAttribute(attrName, virtualDOMElement.props[attrName]);
});
virtualDOMElement.children.forEach((virtualDOMElem) => {
element.appendChild(createDOMElement(virtualDOMElem));
});
return element;
};

export const render = (virtualDOM, node) => {
node.appendChild(createDOMElement(virtualDOM));
};

In index.js , where we actually kick things off, let's say we have the following code:

import { render } from "./react-dom";
const virtualDOM = {
type: "div",
props: { id: "abc", class: "awesome" },
children: [
{
type: "div",
props: { id: "abc", class: "awesome" },
children: ["Hi"],
},
],
};

render(virtualDOM, document.getElementById("app"));

By running the code above in the browser you should see the full DOM structure by inspecting the page and the text Hi on the screen.

Next steps

As you've probably figured out, this is concerned just with creating the DOM structure.

The next thing we wanna tackle it to support diffing between 2 virtual DOM structures.

So basically let's make the following code work properly:

render(virtualDOM, document.getElementById("app"));
render(anotherVirtualDOM, document.getElementById("app"));

By rendering 2 virtual DOMs into the same container, ideally we would like to avoid re-rendering the whole thing and just do a diff between the virtual DOMs and update with that.

Stay tunned for when the next part comes along by following me on Twitter!