In Part 1, I went over various functional-style techniques for cleanly rendering HTML given some JavaScript data. We broke our UI up into component functions, each of which returned a chunk of markup as a function of some data. We then composed these into views that could be reconstructed from new data by making a single function call.
This is the bonus round. In this post, the aim will be to get as close as possible to full-blown, class-based React Component syntax, with VanillaJS (i.e. using native JavaScript with no libraries/frameworks). I want to make a disclaimer that some of the techniques here are not super practical, but I think they’ll still make a fun and interesting exploration of how far JavaScript has come in recent years, and what exactly React does for us.
Article Series:
- Pure Functional Style
- Class Based Components (You are here!)
From functions to classes
Let’s continue using the same example we used in the first post: a blog. Our functional BlogPost component looked like this:
var blogPostData = {
author: 'Brandon Smith',
title: 'A CSS Trick',
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};
function BlogPost(postData) {
return `<div class="post">
<h1>${postData.title}</h1>
<h3>By ${postData.author}</h3>
<p>${postData.body}</p>
</div>`;
}
document.querySelector('body').innerHTML = BlogPost(blogPostData);
In class-based components, we’ll still need that same rendering function, but we’ll incorporate it as a method of a class. Instances of the class will hold their own BlogPost
data and know how to render themselves.
var blogPostData = {
author: 'Brandon Smith',
title: 'A CSS Trick',
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
};
class BlogPost {
constructor(props) {
this.state = {
author: props.author,
title: props.title,
body: props.body
}
}
render() {
return `<div class="post">
<h1>${this.state.title}</h1>
<h3>By ${this.state.author}</h3>
<p>${this.state.body}</p>
</div>`;
}
}
var blogPostComponent = new BlogPost(blogPostData);
document.querySelector('body').innerHTML = blogPostComponent.render();
Modifying state
The advantage of a class-based (object oriented) coding style is that it allows for encapsulation of state. Let’s imagine that our blog site allows admin users to edit their blog posts right on the same page readers view them on. Instances of the BlogPost
component would be able to maintain their own state, separate from the outside page and/or other instances of BlogPost
. We can change the state through a method:
class BlogPost {
constructor(props) {
this.state = {
author: props.author,
title: props.title,
body: props.body
}
}
render() {
return `<div class="post">
<h1>${this.state.title}</h1>
<h3>By ${this.state.author}</h3>
<p>${this.state.body}</p>
</div>`;
}
setBody(newBody) {
this.state.body = newBody;
}
}
However, in any real-world scenario, this state change would have to be triggered by either a network request or a DOM event. Let’s explore what the latter would look like since it’s the most common case.
Handling events
Normally, listening for DOM events is straightforward – just use element.addEventListener()
– but the fact that our components only evaluate to strings, and not actual DOM elements, makes it trickier. We don’t have an element to bind to, and just putting a function call inside onchange
isn’t enough, because it won’t be bound to our component instance. We have to somehow reference our component from the global scope, which is where the snippet will be evaluated. Here’s my solution:
document.componentRegistry = { };
document.nextId = 0;
class Component {
constructor() {
this._id = ++document.nextId;
document.componentRegistry[this._id] = this;
}
}
class BlogPost extends Component {
constructor(props) {
super();
this.state = {
author: props.author,
title: props.title,
body: props.body
}
}
render() {
return `<div class="post">
<h1>${this.state.title}</h1>
<h3>By ${this.state.author}</h3>
<textarea onchange="document.componentRegistry[${this._id}].setBody(this.value)">
${this.state.body}
</textarea>
</div>`;
}
setBody(newBody) {
this.state.body = newBody;
}
}
Okay, there’s quite a bit going on here.
Referencing the component instance
First, we had to get a reference, from within the HTML string, to the present instance of the component. React is able to do this more easily because JSX actually converts to a series of function calls instead of an HTML string. This allows the code to pass this
straight in, and the reference to the JavaScript object is preserved. We, on the other hand, have to serialize a string of JavaScript to insert within our string of HTML. Therefore, the reference to our component instance has to somehow be represented as a string. To accomplish this, we assign each component instance a unique ID at construction time. You don’t have to put this behavior in a parent class, but it’s a good use of inheritance. Essentially what happens is, whenever a BlogPost
instance is constructed, it creates a new ID, stores it as a property on itself, and registers itself in document.componentRegistry
under that ID. Now, any JavaScript code anywhere can retrieve our object if it has that ID. Other components we might write could also extend the Component
class and automatically get unique ID’s of their own.
Calling the method
So we can retrieve the component instance from any arbitrary JavaScript string. Next we need to call the method on it when our event fires (onchange
). Let’s isolate the following snippet and step through what’s happening:
<textarea onchange="document.componentRegistry[${this._id}].setBody(this.value)">
${this.state.body}
</textarea>
You’re probably familiar with hooking up event listeners by putting code inside on_______
HTML attributes. The code inside will get evaluated and run when the event triggers.
document.componentRegistry[${this._id}]
looks in the component registry and gets the component instance by its ID. Remember, all of this is inside a template string, so ${this._id}
evaluates to the current component’s ID. The resulting HTML will look like this:
<textarea onchange="document.componentRegistry[0].setBody(this.value)">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</textarea>
We call the method on that object, passing this.value
(where this
is the element the event is happening on; in our case, <textarea>
) as newBody
.
Updating in response to state changes
Our JavaScript variable’s value gets changed, but we need to actually perform a re-render to see its value reflected across the page. In our previous article, we re-rendered like this:
function update() {
document.querySelector('body').innerHTML = BlogPost(blogPostData);
}
This is another place where we’ll have to make some adjustments for class-style components. We don’t want to throw away and rebuild our component instances every time we re-render; we only want to rebuild the HTML string. The internal state needs to be preserved. So, our objects will exist separately, and we’ll just call render()
again:
var blogPost = new BlogPost(blogPostData);
function update() {
document.querySelector('body').innerHTML = blogPost.render();
}
We then have to call update()
whenever we modify state. This is one more thing React does transparently for us; its setState()
function modifies the state, and also triggers a re-render for that component. We have to do that manually:
// ...
setBody(newBody) {
this.state.body = newBody;
update();
}
// ...
Note that even when we have a complex nested structure of components, there will only ever be one update()
function, and it will always apply to the root component.
Child components
React (along with virtually all other JavaScript frameworks) distinguishes between elements and components that comprise a component and those that are its children. Children can be passed in from the outside, allowing us to write custom components that are containers of other arbitrary content. We can do this too.
class BlogPost extends Component {
constructor(props, children) {
super();
this.children = children;
this.state = {
author: props.author,
title: props.title,
body: props.body
}
}
render() {
return `<div class="post">
<h1>${this.state.title}</h1>
<h3>By ${this.state.author}</h3>
<textarea onchange="document.componentRegistry[${this._id}].setBody(this.value)">
${this.state.body}
</textarea>
<div>
${this.children.map((child) => child.render()).join('')}
</div>
</div>`;
}
setBody(newBody) {
this.state.body = newBody;
update();
}
}
This allows us to write usage code like the following:
var adComponents = ...;
var blogPost = new BlogPost(blogPostData, adComponents);
Which will insert the components into the designated location in the markup.
Concluding thoughts
React seems simple, but it does a lot of subtle things to make our lives much easier. The most obvious thing is performance; only rendering the components whose state updates and drastically minimizing the DOM operations that get performed. But some of the less obvious things are important too.
One of these is that by making granular DOM changes instead of rebuilding the DOM entirely, React preserves some natural DOM state that gets lost when using our technique. Things like CSS transitions, user-resized textareas, focus, and cursor position in an input all get lost when we scrap the DOM and reconstruct it. For our use case, that’s workable. But in a lot of situations, it might not be. Of course, we could make DOM modifications ourselves, but then we’re back to square one, and we lose our declarative, functional syntax.
React gives us the advantages of DOM modification while allowing us to write our code in a more maintainable, declarative style. We’ve shown that vanilla JavaScript can do either, but it can’t get the best of both worlds.
Article Series:
- Pure Functional Style
- Class Based Components (You are here!)
For what it is worth- you never need to use the super or constructor idiom in vanilla JS. You don’t even need to use Class. The module pattern encapsulates all of these concepts while allowing one to organize any mass of code succinctly.
Also, why scope to ‘$’? I know it’s familiar but it’s ugly as sin and confuses the idea behind what it happening.
Classes serve a different purpose from modules. A class doesn’t just encapsulate; it allows multiple instantiations of a particular type of object. It’s true that the same effect could be achieved using prototypes instead of the new class syntax, but the class syntax is much more readable where appropriate, and is a better analog to the framework equivalents of this use case, since most of them either use it themselves, or use TypeScript which has a similar syntax.
I think you’re misunderstanding the usage of
$
. It’s part of the ES6 template literal syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literalsYou’r right on the $. It still feels like nondescript shorthand and I wonder about jQuery confusion.
However, Modules allow reuse very simply with no use of the clunky prototype interface. The Class idiom is no more readable than using a Crockford style revealing Module. I argue that the Module is much cleaner- especially because of super and constructor. It may be more familiar to people using languages that were designed to use Classy structures but it is completely unnecessary and adds more cruft than sugar.
I wasn’t aware of the Crockford module pattern, I thought you were talking about ES6 modules. Having just looked it up, yes, you could fairly easily adapt this technique to use Crockford modules instead of ES6 classes. The difference is a matter of personal preference; for many people (I’d even venture to say most), the standard class syntax is more readable and familiar. But of course it doesn’t allow for private members and is only supported by relatively recent browsers, so there are tradeoffs. For the sake of demonstration, I favored familiarity and similarity to existing solutions over other traits, but in a real-world use case there are arguments for each.
I’ve been waiting for this follow up article. Thanks for writing this, as it helps clarify why someone would use React… it seems for all the hidden stuff that helps you build a web “app”.
By contrast, your vanillaJS example here seems like any old run of the mill template system in JS, nothing more, nothing less. Am I missing something?
My experience with totally JS rendered “websites” is that they are just terrible. Like the super html hacks and table layouts of old, or flash intros, animated GIF spam, etc… the worst of today is a JS “loading” spinner displaying for 10 seconds for a “website” (when it should just be this). sigh
The slowest, most unresponsive, flashiest hunks of junk seem to be powered by bloated JS frameworks. If they just used what this article shows, they’d likely load instantly. But it seems that React, Angular, etc… has turned regular websites in to “apps” to the detriment of the visitor.
I am willing to be taught, as I have had to change a lot over for the past 20 years of webdev, but what is the use of React style “apps” for something like a blog (which is just a website)? A straight forward answer would be appreciated, because at this point React doesn’t seem like a very good solution for just templating, but then again, neither does vanillaJS. Or to ask this question another way, is React the “right tool for the job”, when the job is just a website?
Thanks for reading :)
You’re right that my example is essentially basic templating and not a lot else. The main purpose of the articles was to 1) show people that this is possible to do now, on the client side, without even a regular templating framework like Handlebars.js, and 2) to highlight the benefits a modern framework does add.
There’s much debate going on, over whether or not frameworks like React should be used for all sites or only for “web apps”, and what that even means. Chris Coyier’s post on the topic (https://css-tricks.com/project-need-react/) and Sacha Greif’s counterpoint (https://css-tricks.com/projects-need-react/) were part of what prompted me to write this very article.
My personal stance is that frameworks aren’t worth it for something largely static like a blog, but that a technique like the one I’ve written about here might be (although that’s partially due to my laziness when it comes to configuring a bunch of build tools). As far as templating on the client vs the server, I think that’s more a matter of personal preference than anything else. Some vanilla JavaScript without a framework probably isn’t going to noticeably increase page load time, but I also haven’t tried out my own strategy in a real-world site yet, so it’s possible it has some limitations that only become apparent when you try and scale it up.
Thanks Vanderson and Brandon for hashing this out. Been on my mind for a while :)
Brandon, Thank you for an excellent article! Could you supplement the article with live examples on codepen? The last 2 examples I refused to work. thanks in advance
Hello Brandon, thanks for your post.
It was a light in my brain, I could learn a lot with it.
I’m seeing just an error on this expression:
${this.children.map((child) => child.render()).join(”)}
It need an return before child.render(), because withou a return it’ll have nothing to output.
Actually, in ES6 arrow functions, if you only have one statement in the function body you can omit the curly braces and even the return keyword :)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions