I was a little inspired this morning by a tweet from Kent C. Dodds
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
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:
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 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?
- 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.
- There are also no other lifecycle hooks that React provides, like
componentDidUpdate, but those have analogues; the component is mounted when the main function is called for the first time, and
mutatorsare called on state updates.
- 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:
JSX is a stellar invention, even with React out of the picture.
If you’ve worked with React — and even if you haven’t — you’ve probably heard of JSX. It’s that weird…