Building a React-esque component using vanilla javascript.

I was a little inspired this morning by a tweet from Kent C. Dodds

Full disclosure: I’m a little bored and a little hungover this morning, so apologies if this goes on a tangent. But I saw one thing was notably absent from Kent’s list: vanilla javascript!

So let’s do it the simplest possible way:

This works, but it’s kinda boring and not very scalable. It stores state in the DOM, which is a pattern we left behind in the 2000s. Plus you would need to copy and paste all of the code to re-render it anywhere.

What if we took some lessons from React, and turned this into a reusable component , with state separated out a bit more from the rendering logic?

This is a little closer to something we’d want to ship. But after using JSX for a while, manually calling document.createElement and element.appendChild feels kinda nasty.

Thankfully, JSX is actually totally decoupled from React. You can literally just write your own JSX renderer, and tell babel to use it using a /* @jsx myRenderer */ comment. It’s pretty simple to write a renderer which turns JSX into plain old DOM elements:

This jsxToDom implementation leaves a lot to be desired, and doesn’t cover nearly enough corner-cases to be production-ready. But for the sake of this example it works; it will create a DOM node with all of the desired attributes and event listeners, meaning we don’t need to do any of that manually.

Anyway — let’s use that new jsxToDom JSX pragma in our counter component.

It’s much cleaner, but there’s still some stuff here that could be better. Updating the DOM is still very tightly coupled to updating the state. We have to call updateButton manually every time state.count changes, which is going to be messy if we introduce more event handlers which change state.

In React that’s taken care of by setState, which triggers a re-render any time state changes. Can we do something similar in vanilla javascript? Probably!

We’ve build setState so rather than directly mutating state, it maps our old state to our new state. That’s a lot closer to React, and a lot more functional. Those setState calls will also trigger any DOM updates now.

Why not just do a full re-render every time state changes? That would be problematic because the UI would need to totally reset on each update. Form fields would be cleared, and the experience would be pretty terrible.

So what’s missing versus React?

  1. We’re still having to separate out the initial render, and subsequent updates/modifications to the UI as state changes. React handles that by diffing two virtual DOMs and mapping the differences to the real DOM — which means your code can just implement a nice, single declarative render function. We’re not going to achieve that here without a lot of heavy lifting.
  2. We have a way of passing props to components for the initial render, but no way of updating props through the lifecycle of our app. That’s obviously a huge part of building a scalable app using the component pattern. Maybe there’s a clean way to expand this pattern to do that with vanilla javascript. But for now that’s left as an exercise to the reader.
  3. There are also no other lifecycle hooks that React provides, like componentDidMount or componentDidUpdate, but those have analogues; the component is mounted when the main function is called for the first time, and mutators are called on state updates.
  4. React batches state updates before it does a re-render. That’s totally possible here, but it’s more code than I want to write since we’re keeping this simple.

Would I build a full app like this for production use? Maybe as an experiment, maybe not. But it is interesting that we can get some distance towards a lot of the code structure and organization that React brings to the table, without bringing a new framework to the table. Another lesson is that JSX can be used for any general templating purposes, even outside of the confines of React.

Anyone want to try taking this line of thinking even further?


As a footnote: Some time after writing this article I threw together a module called jsx-pragmatic to make it easier to write your own jsx renderer, and use jsx to directly render html and dom nodes. If you’re interested you can read about it here:

Treating JSX as a standalone renderer is a pattern we use at PayPal for zoid components, when we want to do one-off pre-renders on third-party sites. We don’t want to ship a full component engine like React to sites we don’t own. And with vanilla JSX, all of the templates are compiled at build-time to vanilla javascript, so it’s about as lightweight as you can get.