Emulating React and JSX in Vanilla JS
Not everybody likes JavaScript frameworks, nor are they always applicable. But even when we don’t want to or can’t use them, there are some parts that are handy to have.
In this tutorial, Toptal Freelance JavaScript Developer Juan Carlos Arias Ambriz proposes a simple way to recapture some of the benefits of using JSX while working in vanilla JS.
Not everybody likes JavaScript frameworks, nor are they always applicable. But even when we don’t want to or can’t use them, there are some parts that are handy to have.
In this tutorial, Toptal Freelance JavaScript Developer Juan Carlos Arias Ambriz proposes a simple way to recapture some of the benefits of using JSX while working in vanilla JS.
Juan has 10+ years of freelance UX experience. His work is rooted in perfectionism & providing the best experience to user.
Expertise
PREVIOUSLY AT
Few people dislike frameworks, but even if you are one of them, you should take note and adopt the features that make life a little easier.
I was opposed to the use of frameworks in the past. However, lately, I’ve had the experience of working with React and Angular in some of my projects. The first couple of times I opened my code editor and started writing code in Angular it felt weird and unnatural; especially after more than ten years of coding without using any frameworks. After a while, I decided to commit to learning these technologies. Very quickly one big difference became apparent: it was so easy to manipulate the DOM, so easy to adjust the order of the nodes when needed, and it didn’t take pages and pages of code to build a UI.
Although I still prefer the freedom of not being attached to a framework or an architecture, I couldn’t ignore the fact that creating DOM elements in one is much more convenient. So I started looking at ways to emulate the experience in vanilla JS. My goal is to extract some of those ideas from React and demonstrate how the same principles can be implemented in plain JavaScript (often referred to as vanilla JS) to make developer’s lives a little bit easier. To accomplish this, let’s build a simple app for browsing GitHub projects.
Whichever way we are building a front end using JavaScript, we will be accessing and manipulating the DOM. For our application, we will need to construct a representation of each repository (thumbnail, name, and description), and add it to the DOM as a list element. We will be using the GitHub Search API to fetch our results. And, since we’re talking about JavaScript, let’s search the JavaScript repositories. When we query the API, we get the following JSON response:
{
"total_count": 398819,
"incomplete_results": false,
"items": [
{
"id": 28457823,
"name": "freeCodeCamp",
"full_name": "freeCodeCamp/freeCodeCamp",
"owner": {
"login": "freeCodeCamp",
"id": 9892522,
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/freeCodeCamp",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
"description": "The https://freeCodeCamp.org open source codebase "+
"and curriculum. Learn to code and help nonprofits.",
// more omitted information
}, //...
]
}
React’s approach
React makes it very simple to write HTML elements into the page and is one of the features I’ve always wanted to have while writing components in pure JavaScript. React uses JSX, which is very similar to regular HTML.
However, that’s not what the browser reads.
Under the hood, React transforms JSX to a bunch of calls to a React.createElement
function. Let’s take a look at an example of JSX using one item from the GitHub API and see what it translates to.
<div className="repository">
<div>{item.name}</div>
<p>{item.description}</p>
<img src={item.owner.avatar_url} />
</div>;
;
React.createElement(
"div",
{ className: "repository" },
React.createElement(
"div",
null,
item.name
),
React.createElement(
"p",
null,
item.description
),
React.createElement(
"img",
{ src: item.owner.avatar_url }
)
);
JSX is very simple. You write regular HTML code and inject data from the object by adding curly brackets. JavaScript code within the brackets will be executed, and the value will be inserted in the resulting DOM. One of the advantages of JSX is that React creates a virtual DOM (a virtual representation of the page) to track changes and updates. Instead of rewriting the entire HTML, React modifies the DOM of the page whenever the information is updated. This is one of the main issues React was created to solve.
jQuery approach
Developers used to use jQuery a lot. I’d like to mention it here because it is still popular and also because it’s pretty close to the solution in pure JavaScript. jQuery gets a reference to a DOM node (or a collection of DOM nodes) by querying the DOM. It also wraps that reference with various functionalities for modifying its contents.
While jQuery has its own DOM construction tools, the thing I see most often in-the-wild is just HTML concatenation. For example, we can insert HTML code into the selected nodes by calling the html()
function. According to jQuery documentation, if we want to change the contents of a div
node with the class demo-container
we can do it like this:
$( "div.demo-container" ).html( "<p>All new content.<em>You bet!</em></p>" );
This approach makes it easy to create DOM elements; however, when we need to update nodes, we need to query the nodes we need or (more commonly) fall back to recreating the whole snippet whenever an update is required.
DOM API approach
JavaScript in browsers has a built-in DOM API that gives us direct access to creating, modifying, and deleting the nodes in a page. This is reflected in React’s approach, and by using the DOM API, we get one step closer to the benefits of that approach. We are modifying only the elements of the page that actually require to be changed. However, React also keeps track of a separate virtual DOM. By comparing the differences between the virtual and the actual DOM, React is then able to identify which parts require modification.
Those extra steps are sometimes useful, but not always, and manipulating the DOM directly can be more efficient. We can create new DOM nodes using _document.createElement_
function, which will return a reference to the node created. Keeping track of these references gives us an easy way to modify only the nodes that contain the part that needs to be updated.
Using the same structure and data source as in the JSX example, we can construct the DOM in the following way:
var item = document.createElement('div');
item.className = 'repository';
var nameNode = document.createElement('div');
nameNode.innerHTML = item.name
item.appendChild(nameNode);
var description = document.createElement('p');
description.innerHTML = item.description;
item.appendChild(description );
var image = new Image();
Image.src = item.owner.avatar_url;
item.appendChild(image);
document.body.appendChild(item);
If the only thing on your mind is the efficiency of code execution, then this approach is very good. However, efficiency is not measured merely in execution speed, but also in ease of maintenance, scalability, and plasticity. The problem with this approach is that it is very verbose and sometimes convoluted. We need to write a bunch of function calls even if we are merely constructing a basic structure. The second big disadvantage is the sheer number of variables created and tracked. Let’s say, a component you’re working contains on its own 30 DOM elements, you’ll need to create and use 30 different DOM elements and variables. You can reuse some of them and do some juggling at the expense of maintainability and plasticity, but it can become really messy, really quickly.
Another significant disadvantage is due to the number of lines of code you need to write. With time, it becomes harder and harder to move elements from one parent to another. That’s one thing I really appreciate from React. I can view JSX syntax and get in a few seconds which node is contained, where, and change if required. And, while it might seem like it’s not a big a deal at first, most projects have constant changes that will make you look for a better way.
Proposed Solution
Working directly with the DOM works and gets the job done, but it also makes constructing the page very verbose, especially when we need to add HTML attributes and nest nodes. So, the idea would be to capture some of the benefits of working with technologies like JSX and make our lives simpler. The advantages we are trying to replicate are the following:
- Write code in HTML syntax so that the creation of DOM elements becomes easy to read and modify.
- Since we aren’t using an equivalent of virtual DOM like in the case of React, we need to have an easy way to indicate and keep track of the nodes we are interested in.
Here’s a simple function that would accomplish this using an HTML snippet.
Browser.DOM = function (html, scope) {
// Creates empty node and injects html string using .innerHTML
// in case the variable isn't a string we assume is already a node
var node;
if (html.constructor === String) {
var node = document.createElement('div');
node.innerHTML = html;
} else {
node = html;
}
// Creates of uses and object to which we will create variables
// that will point to the created nodes
var _scope = scope || {};
// Recursive function that will read every node and when a node
// contains the var attribute add a reference in the scope object
function toScope(node, scope) {
var children = node.children;
for (var iChild = 0; iChild < children.length; iChild++) {
if (children[iChild].getAttribute('var')) {
var names = children[iChild].getAttribute('var').split('.');
var obj = scope;
while (names.length > 0)
{
var _property = names.shift();
if (names.length == 0)
{
obj[_property] = children[iChild];
}
else
{
if (!obj.hasOwnProperty(_property)){
obj[_property] = {};
}
obj = obj[_property];
}
}
}
toScope(children[iChild], scope);
}
}
toScope(node, _scope);
if (html.constructor != String) {
return html;
}
// If the node in the highest hierarchy is one return it
if (node.childNodes.length == 1) {
// if a scope to add node variables is not set
// attach the object we created into the highest hierarchy node
// by adding the nodes property.
if (!scope) {
node.childNodes[0].nodes = _scope;
}
return node.childNodes[0];
}
// if the node in highest hierarchy is more than one return a fragment
var fragment = document.createDocumentFragment();
var children = node.childNodes;
// add notes into DocumentFragment
while (children.length > 0) {
if (fragment.append){
fragment.append(children[0]);
}else{
fragment.appendChild(children[0]);
}
}
fragment.nodes = _scope;
return fragment;
}
The idea is simple but powerful; we send the function the HTML we want to create as a string, in the HTML string we add a var attribute to the nodes we want to have references created for us. The second parameter is an object in which those references will be stored. If not specified we will create a “nodes” property on the returned node or document fragment (in case the highest hierarchy node is more than one). Everything is accomplished in less than 60 lines of code.
The function works in three steps:
- Create a new empty node and use innerHTML in that new node to create the entire DOM structure.
- Iterate over the nodes and if the var attribute exists, add a property in the scope object that points to the node with that attribute.
- Return the top node in the hierarchy, or a document fragment in case there are more than one.
So how does our code for rendering the example look now?
var UI = {};
var template = '';
template += '<div class="repository">'
template += ' <div var="name"></div>';
template += ' <p var="text"></p>'
template += ' <img var="image"/>'
template += '</div>';
var item = Browser.DOM(template, UI);
UI.name.innerHTML = data.name;
UI.text.innerHTML = data.description;
UI.image.src = data.owner.avatar_url;
First, we define the object (UI) where we will store the references to the created nodes. We then compose the HTML template we are going to be using, as a string, marking the target nodes with a “var” attribute. After that, we call the function Browser.DOM with the template and the empty object that will store the references. Finally, we use the stored references to place the data inside the nodes.
This approach also separates building the DOM structure and inserting the data into separate steps which helps with keeping the code organized and well structured. This enables us to separately create the DOM structure and fill-in (or update) the data when it becomes available.
Conclusion
While some of us dislike the idea of switching over to frameworks and handing over control, it is important that we recognize the benefits those frameworks bring. There is a reason why they’re so popular.
And while a framework might not always suit your style or needs, some functionalities and techniques can be adopted, emulated or sometimes even decoupled from a framework. Some things will always be lost in translation, but a lot can be gained and used at a tiny fraction of the cost a framework carries.
Understanding the basics
What is JSX?
JSX is a React component that simplifies the syntax and the process of creating HTML templates and DOM elements. JSX is written as HTML inline in the source code but gets transliterated to JavaScript instructions for DOM construction, thus getting the best of both worlds.
What is the virtual DOM in React?
Virtual DOM is React’s representation of the DOM. By comparing it with the page DOM it is possible to track changes and only modify the parts of the page that actually need to be modified, rather than queueing large portions of the page for parsing and re-rendering.
Juan Carlos Arias Ambriz
Guadalajara, Mexico
Member since June 6, 2016
About the author
Juan has 10+ years of freelance UX experience. His work is rooted in perfectionism & providing the best experience to user.
Expertise
PREVIOUSLY AT