Reactive UI’s with VanillaJS – Part 1: Pure Functional Style

Last month Chris Coyier wrote a post investigating the question, “When Does a Project Need React?” In other words, when do the benefits of using React (acting as a stand-in for data-driven web frameworks in general), rather than server-side templates and jQuery, outweigh the added complexity of setting up the requisite tooling, build process, dependencies, etc.? A week later, Sacha Greif wrote a counterpoint post arguing why you should always use such a framework for every type of web project. His points included future-proofing, simplified workflow from project to project (a single architecture; no need to keep up with multiple types of project structures), and improved user experience because of client-side re-rendering, even when the content doesn’t change very often.

In this pair of posts, I delve into a middle ground: writing reactive-style UI’s in plain old JavaScript – no frameworks, no preprocessors.

Article Series:

  1. Pure Functional Style (You are here!)
  2. Class Based Components (Coming soon!)

There are two very different ways to write React components.

  1. You can write them as classes. Stateful objects with lifecycle hooks and internal data.
  2. Or, you can write them as functions. Just a piece of HTML that gets constructed and updated based on parameters that are passed in.

The former is often more useful for large, complex applications with lots of moving parts, while the latter is a more elegant way to display information if you don’t have a lot of dynamic state. If you’ve ever used a templating engine like Handlebars or Swig, their syntax looks pretty similar to function-style React code.

In this pair of posts, our target use case is websites that might otherwise be static, but would benefit from JavaScript-based rendering were it not for the overhead of setting up a framework like React. Blogs, forums, etc. Therefore, this first post will focus on the functional approach to writing a component-based UI, because it’ll be more practical for that type of scenario. The second post will be more of an experiment; I’ll really push the limit on how far we can take things without a framework, trying to replicate React’s class-based component pattern as closely as possible with only Vanilla JavaScript, probably at the expense of some practicality.

About functional programming

Functional programming has soared in popularity over the last couple years, driven primarily by Clojure, Python, and React. A full explanation of functional programming is outside the scope of this post, but the part that’s relevant to us right now is the concept of values that are functions of other values.

Let’s say your code needs to represent the concept of a rectangle. A rectangle has width and height, but it also has area, perimeter, and other attributes. At first, one might think to represent a rectangle with the following object:

var rectangle = {   width: 2,   height: 3,   area: 6,   perimeter: 10 };

But, it would quickly become apparent that there’s a problem. What happens if the width gets changed? Now we have to also change the area and perimeter, or they’d be wrong. It’s possible to have conflicting values, where you can’t just change one without the possibility of having to update something else. This is called having multiple sources of truth.

In the rectangle example, the functional programming-style solution is to make area and perimeter into functions of a rectangle:

var rectangle = {   width: 2,   height: 3 };  function area(rect) {   return rect.width * rect.height; }  function perimeter(rect) {   return rect.width * 2 + rect.height * 2; }  area(rectangle); // = 6 perimeter(rectangle); // = 10

This way, if width or height changes, we don’t have to manually modify anything else to reflect that fact. The area and perimeter just are correct. This is called having a single source of truth.

This idea is powerful when you substitute the rectangle with whatever data your application may have, and the area and perimeter with HTML. If you can make your HTML a function of your data, then you only have to worry about modifying data – not DOM – and the way it gets rendered on the page will be implicit.

UI components as functions

We want to make our HTML a function of our data. Let’s use the example of a blog post:

var blogPost = {   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 PostPage(postData) {   return  '<div class="page">' +             '<div class="header">' +                'Home' +               'About' +               'Contact' +             '</div>' +              '<div class="post">' +                '<h1>' + postData.title + '</h1>' +                '<h3>By ' + postData.author + '</h3>' +               '<p>' + postData.body + '</p>' +             '</div>' +           '</div>'; }  document.querySelector('body').innerHTML = PostPage(blogPost);

Okay. We’ve made a function of a post object, which returns an HTML string that renders our blog post. It’s not really “componentized” though. It’s all one big thing. What if we also wanted to render all our blog posts in a sequence on the home page? What if we wanted to reuse that header across different pages? Luckily, it’s really easy to build functions out of other functions. This is called composing functions:

var blogPost = {   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 Header() {   return '<div class="header">' +              'Home' +             'About' +             'Contact' +           '</div>'; }  function BlogPost(postData) {   return '<div class="post">' +              '<h1>' + postData.title + '</h1>' +              '<h3>By ' + postData.author + '</h3>' +             '<p>' + postData.body + '</p>' +           '</div>'; }  function PostPage(postData) {   return  '<div class="page">' +             Header() +             BlogPost(postData) +           '</div>'; }  function HomePage() {   return '<div class="page">' +             Header() +             '<h1>Welcome to my blog!</h1>' +             '<p>It\'s about lorem ipsum dolor sit amet, consectetur ad...</p>' +           '</div>'; }  document.querySelector('body').innerHTML = PostPage(blogPost);

That’s so much nicer. We didn’t have to duplicate the header for the home page; we have a single source of truth for that HTML code. If we wanted to display a post in a different context, we could do so easily.

Prettier syntax with template literals

Okay, but all those plus signs are horrible. They’re a pain to type, and they make it harder to read what’s going on. There has to be a better way, right? Well, the folks at W3C are way ahead of you. They created template literals – which, while still relatively new, have pretty good browser support at this point. Simply wrap your string in backticks instead of quotes, and it will get a couple of extra superpowers.

The first superpower is the ability to span multiple lines. So our BlogPost component up above can become:

// ...  function BlogPost(postData) {   return `<div class="post">             <h1>` + postData.title + `</h1>             <h3>By ` + postData.author + `</h3>             <p>` + postData.body + `</p>           </div>`; }  // ...

That’s nice. But the other power is even nicer: variable substitution. Variables (or any JavaScript expression, including function calls!) can be inserted directly into the string if they’re wrapped in $ { }:

// ...  function BlogPost(postData) {   return `<div class="post">             <h1>$  {postData.title}</h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }  // ...

Much better. It almost looks like JSX now. Let’s see our full example again, with template literals:

var blogPost = {   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 Header() {   return `<div class="header">             Home             About             Contact           </div>`; }  function BlogPost(postData) {   return `<div class="post">             <h1>$  {postData.title}</h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }  function PostPage(postData) {   return  `<div class="page">             $  {Header()}             $  {BlogPost(postData)}           </div>`; }  function HomePage() {   return `<div class="page">             $  {Header()}             <h1>Welcome to my blog!</h1>             <p>It's about lorem ipsum dolor sit amet, consectetur ad...</p>           </div>`; }  document.querySelector('body').innerHTML = PostPage(blogPost);

More than just filling in blanks

So we can fill in variables, and even other components through functions, but sometimes more complex rendering logic is necessary. Sometimes we need to loop over data, or respond to a condition. Let’s go over some JavaScript language features that make it easier to do more complex rendering in a functional style.

The ternary operator

We’ll start with the simplest logic: if-else. Of course, since our UI components are just functions, we could use an actual if-else if we wanted to. Let’s see what that would look like:

var blogPost = {   isSponsored: true,   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) {   var badgeElement;   if(postData.isSponsored) {     badgeElement = `<img src="badge.png">`;   } else {     badgeElement = '';   }    return `<div class="post">             <h1>$  {postData.title} $  {badgeElement}</h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }

That’s… not ideal. It adds a whole lot of lines for something that isn’t that complicated, and it separates part of our rendering code from its place within the rest of the HTML. This is because a classical if-else statement decides which lines of code to run, rather than which value to evaluate to. This is an important distinction to understand. You can only stick an expression into a template literal, not a series of statements.

The ternary operator is like an if-else, but for an expression instead of a set of statements:

var wantsToGo = true; var response = wantsToGo ? 'Yes' : 'No'; // response = 'Yes'  wantsToGo = false; response = wantsToGo ? 'Yes' : 'No'; // response = 'No'

It takes the form [conditional] ? [valueIfTrue] : [valueIfFalse]. So, the blog post example above becomes:

var blogPost = {   isSponsored: true,   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} $  {postData.isSponsored ? '<img src="badge.png">' : ''}             </h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }

Much better.

Array.map()

On to loops. Anytime we have an array of data that we want to render, we’re going to need to loop over those values to generate the corresponding HTML. But if we used a for-loop, we’d run into the exact same issue we had with the if-else statement above. A for loop doesn’t evaluate to a value, it executes a series of statements in a certain way. Luckily, ES6 added some very helpful methods to the Array type which serve this specific need.

Array.map() is an Array method that takes a single argument, which is a callback function. It loops over the array it’s called on (similar to Array.forEach()), and calls the supplied callback once for each item, passing the array element to it as an argument. The thing that makes it different from Array.forEach() is that the callback is supposed to return a value – presumably one that’s based on the corresponding item in the array – and the full expression returns the new array of all the items returned from the callback. For example:

var myArray = [ 'zero', 'one', 'two', 'three' ];  // evaluates to [ 'ZERO', 'ONE', 'TWO', 'THREE' ] var capitalizedArray = myArray.map(function(item) {   return item.toUpperCase(); });

You might be able to guess why this is so useful for what we’re doing. Earlier we established the concept of a value being a function of another value. Array.map() allows us to get an entire array, for which each item is a function of the corresponding item in another array. Let’s say we have an array of blog posts that we want to display:

function BlogPost(postData) {   return `<div class="post">             <h1>$  {postData.title}</h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }  function BlogPostList(posts) {   return `<div class="blog-post-list">             $  {posts.map(BlogPost).join('')}           </div>` }  var allPosts = [   {     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.'   },   {     author: 'Chris Coyier',     title: 'Another CSS Trick',     body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'   },   {     author: 'Bob Saget',     title: 'A Home Video',     body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'   } ]  document.querySelector('body').innerHTML = BlogPostList(allPosts);

Each object containing the info for a single blog post is passed, one by one, to the BlogPost function, and the returned HTML strings are placed into a new array. We then just call join() on that new array to combine the array of strings into a single string (separated by an empty string), and we’re done. No for-loops, just a list of objects converted to a list of HTML elements.

Re-rendering

We can now implicitly generate HTML for given data, in a way that’s reusable and composable, all within the browser. But, how do we update when the data changes? How do we even know when to trigger an update? This subject is one of the most complex and hotly debated in the JavaScript framework community today. Making large numbers of DOM updates efficiently is an amazingly difficult problem, one which engineers at Facebook and Google have spent years working on.

Luckily, our proverbial website is just a blog. The content pretty much only changes when we look at a different blog post. There aren’t a ton of interactions to detect, we don’t have to optimize our DOM operations. When we load a new blog post, we can just scrap the DOM and rebuild it.

document.querySelector('body').innerHTML = PostPage(postData);

We could make this a little nicer by wrapping it in a function:

function update() {   document.querySelector('body').innerHTML = PostPage(postData); }

Now whenever we load a new blog post, we can just call update() and it will appear. If our application were complicated enough that it needed to re-render frequently – maybe a couple times per second in certain situations – it would get choppy really fast. You could write complex logic to figure out which sections of the page truly need to update given a particular change in data and only update those, but that’s the point where you should just use a framework.

Not just for content

At this point pretty much all our rendering code has been used to determine the actual HTML and text content inside the elements, but we don’t have to stop there. Since we’re just creating an HTML string, anything inside there is fair game. CSS classes?

function BlogPost(postData) {   return `<div class="post $  {postData.isSponsored ? 'sponsored-post' : ''}">             <h1>               $  {postData.title} $  {postData.isSponsored ? '<img src="badge.png">' : ''}             </h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }

Check. HTML attributes?

function BlogPost(postData) {   return `<div class="post $  {postData.isSponsored ? 'sponsored-post' : ''}">             <input type="checkbox" $  {postData.isSponsored ? 'checked' : ''}>             <h1>               $  {postData.title} $  {postData.isSponsored ? '<img src="badge.png">' : ''}             </h1>             <h3>By $  {postData.author}</h3>             <p>$  {postData.body}</p>           </div>`; }

Check. Feel free to get really creative with this. Think about your data, and think about how all the different aspects of it should be represented in markup, and write expressions that turn one into the other.

Summary

Hopefully, this post gives you a good set of tools for writing simple reactive, data-driven web interfaces without the overhead of any tools or frameworks. This type of code is far easier to write and maintain than jQuery spaghetti, and there’s no hurdle at all to using it right now. Everything we’ve talked about here comes free with all reasonably modern browsers, without so much as a library.

Part 2 will focus on class-based, stateful components, which will get near the territory of too-complicated-to-reasonably-do-in-VanillaJS. But by golly, we’re going to try anyway, and it’s going to be interesting.

Article Series:

  1. Pure Functional Style (You are here!)
  2. Class Based Components (Coming soon!)

Reactive UI’s with VanillaJS – Part 1: Pure Functional Style is a post from CSS-Tricks

CSS-Tricks

Add a Comment

Your email address will not be published. Required fields are marked *

21