Building a React-Like Library: Functional Components
This is the second of four issues where we build a React-like library to learn how React works under the hood.
Building a React-Like Library
The series contains the following articles:
Functional Components. This issue.
Implement useState. Pending.
Implement diffing. Pending.
Video
If you prefer, I recorded a live-coding video explaining this article. Feel free to complement this article with it, or go and watch that instead.
Introduction
In the previous article, we built a site from a static virtual DOM. You can find the code in this gist.
const render = (vnode, parent) => {
// manage string type
if (typeof vnode === "string") {
return parent.appendChild(document.createTextNode(vnode));
}
// create HTML element
const element = document.createElement(vnode.tag);
// set attributes
if (vnode.props) {
Object.keys(vnode.props).forEach(key => {
const value = vnode.props[key];
element.setAttribute(key, value);
});
}
// iterate over children and render them
if (vnode.children) {
vnode.children.forEach(child => render(child, element));
}
// append the element created
return parent.appendChild(element);
};
const vDom = {
tag: "div",
props: {},
children: [
{
tag: "h1",
props: { id: "title" },
children: ["Hello, World!"]
},
{
tag: "p",
props: {},
children: ["I'm learning about virtual DOM"]
}
]
};
render(vDom, document.getElementById("root"));
Build a Node With a Function
A node is an object with tag
, props
, and children
:
{
tag: “h1”,
props: { id: “title” },
children: ["Hello, World!"]
}
// or
{
tag: "div",
props: {},
children: [...]
}
Let’s build the node with a function.
const createElement = (tag, props, children) => ({ tag, props, children });
Let’s use this function:
const node = createElement(“h1”, { id: “title” }, [“Hello from app!”]);
Building the Virtual DOM With a Function
To create all the virtual DOM with createElement
, all the children nodes need to be created also with createElement
:
const vDom = createElement(
"div",
{},
[
createElement("h1", {}, ["Hello from app!"]),
createElement("p", {}, ["This was built with createElement"])
]
);
That’s it; now our vDom
is the same as before, but we built it with the function createElement
. Which is similar to what React does with React.createElement
.
Functional Components
One of the main features of React is the functional components. Let’s refactor the h1
to be a functional component:
const Title = () => createElement("h1", {}, ["Hello from dynamic app!"]);
Our virtual DOM now has a functional component:
Let’s build our vDom again:
const vDom = createElement(
"div",
{},
[
// Functional component
createElement(Title, {}, []),
// createElement("h1", {}, ["Hello from app!"])
createElement("p", {}, ["This was built with createElement"])
]
);
Render Function With Functional Components
Notice how now one node uses the functional component;
createElement(Title, {}, []),
The render
function doesn’t know what to do with a “tag” that is a function. It can only handle tags as strings:
const render = (vnode, parent) => {
//…
// create HTML element
const element = document.createElement(vnode.tag);
//…
}
This “tag” function is our functional component, which returns a node when called:
const Title = () => createElement("h1", {}, ["Hello from dynamic app!"]);
We call the “tag” function and pass the returned value (another node) to the render function:
const render = (vnode, parent) => {
// ...
// manage function type
if (typeof vnode.tag === "function") {
// call function to get the virtual DOM node
const nextVnode = vnode.tag();
return render(nextVnode, parent);
}
// ...
}
And now it works! Our React-like “library” can now render functional components.
Put It Together
I recorded a live-coding video explaining this article. It might be helpful as a recap also.
Let’s take a look at it all together. First, our new virtual DOM:
const Title = () => createElement("h1", {}, ["Hello from dynamic app!"]);
const vDom = createElement(
"div",
{},
[
createElement(Title, {}, []),
createElement("p", {}, ["This was built with createElement"])
]
);
Title
is a functional component, but now our render
function knows how to deal with functional components:
const render = (vnode, parent) => {
// manage string type
if (typeof vnode === "string") {
return parent.appendChild(document.createTextNode(vnode));
}
// manage functional component
if (typeof vnode.tag === "function") {
const nextVnode = vnode.tag();
render(nextVnode, parent);
return;
}
// create HTML element
const element = document.createElement(vnode.tag);
// set attributes
if (vnode.props) {
Object.keys(vnode.props).forEach(key => {
const value = vnode.props[key];
element.setAttribute(key, value);
});
}
// iterate over children and render them
if (vnode.children) {
vnode.children.forEach(child => render(child, element));
}
// append the element created
return parent.appendChild(element);
};
Finally, we render the vDom
into the website:
render(vDom, document.getElementById("root"));
IMHO, the next issue is the most impressive. We’ll create interactive applications by adding state to the functional components (useState)! Stay tuned!
Thanks to Bernat and Sebastià for reviewing this article 🙏