In this post, I’ll talk about and compare several libraries that let you use React from ClojureScript. First, though: what’s so great about React to begin with?
In the Backbone version, we have a
TodoView with a
render function. That function’s job is to get the initial DOM onto the page for a single todo item. Once it’s there, there are several things we can do to it: we can make it invisible (
toggleVisible), we can complete it (
toggleCompleted), we can edit it (
edit). Some of these operations push changes back to the data model, while some are used to update the DOM, and some do both at the same time. This is the central problem in any DOM-based view system: keeping the DOM in sync with the data. We’ve got two mutable representations of the same data, and we need to update each of them in sync. Eventually, we’re going to fail, get a bad headache, or both.
It would be a lot nicer to just update the data model, our canonical representation of the data, and figure out the UI from there. Why not completely re-render the entire page every time the data changes? Then the page will always be up to date.
Theoretically, it’s a great idea. It only falls down—as so many great naive ideas do—because it’s slow. But it’s not actually all that rending code that’s slow. It’s the DOM. I said the stateful-ness of the DOM was the big problem, but there’s a second one: the DOM is slow to manipulate. Changing the DOM triggers all sorts of things in the browser’s rendering system. If you re-render the entire page when you mark a todo item as complete, you’ve just made the browser recalculate all your element sizes and their interactions—along with who knows what else—just to display almost the exact same page. What a waste.
By functionally, I mean that your DOM is now literally a function of your data. To illustrate, have a look at the
But what about ClojureScript?
React makes your rendering as simple as a defining a function. It’s no surprise, then, that the ClojureScript world has embraced React with open arms. In fact, there are already several libraries that help you use React in ClojureScript. Here are the big ones.
Om is by far the most popular of the ClojureScript-React bridges. It’s also perhaps the most heavy-handed. It does a great deal for you, which is good if you want it all, and less good if you don’t. It’s the most “framework-y” of all of these.
Om expects you to store your application state in a single atom. The effect of this is that the only way to change anything in that state is to change the root of the state: the atom. Most of the time, you’ll work with some piece of the state; for instance, a todo item only needs to know about its title and whether it’s completed. To mark a todo item as complete, you’d have to
swap! the value of the atom using a giant
update-in to find the
:completed value for the todo you’re rendering.
To solve that problem, Om provides the application state as a cursor. A cursor bundles together the piece of state it’s looking at (a single todo item), the path to get there (something like
[:todos 3]; the kind of thing you’d pass to
update-in), and the root state atom. Finally, Om provides a function called
transact!, which operates on a cursor, updating the root state atom to contain a state in which the piece of the state the cursor is looking at has changed. That’s a brainful. Think of it this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
So cursors are pretty great. The Cursors page from the Om wiki has a good deal more to say about them.
Cursors make it easy to keep your immutable state in a single point of mutability while also passing around pieces of that state to sub-components.
Om watches the state atom for changes and notices when your components need to re-render.
Your application state needs to be a tree. It can only consist of maps and vectors (and then whatever you want at the leaf ends: strings, numbers, etc.) This is because cursors need to be able to trace a path through your state structure to a particular point. Not all data is easily formatted this way.
Om requires your to keep your state in a (single) atom. That’s usually what you want anyway, though.
Om feels to me like several good ideas fused (complected?) together. Cursors, state management, controlling React: each of these things could probably stand alone, but it’s not obvious how to tease them apart. Someday I imagine they’ll be separated, but for now, if you want one you often want them all anyhow.
As a garnish: om-tools
Prismatic uses Om to render their app, and they’ve developed a goodie bag of tricks to make using Om even nicer, called om-tools. Most of it’s syntactic sugar, but it also offers data validation and mixins—both features of React that aren’t available in vanilla Om. If you’re interested in Om, check out om-tools as well.
(TodoMVC demo at bottom of page)
Reagent is the React bridge I know the least about, so I won’t try to say too much about it. Its big idea is its atoms.
Reagent provides its own special kind of atom. It works like an ordinary
clojure.core atom, except that it also keeps track of which components
deref it. Then, when the value of the atom changes, those components are re-rendered.
I’ll be honest: it freaks me out a bit. That’s too magical for my taste. It feels very “easy” and not very simple. But hey, maybe that’s your thing. I can respect that. it’s worth checking out.
Quiescent is little more than a thin syntax wrapper for React packaged as a library. It makes component definition look just like ordinary function definition.
It does one other thing, though: it defines
shouldComponentUpdate for you.
shouldComponentUpdate is your opportunity to skip re-rendering your component this time around. In general, it’s safe to skip rendering a component if the data that feeds it is the same this time as the last time. Quiescent does that check for you. ClojureScript’s persistent data structures mean that a simple equality check (
clojure.core/=) is all you need. That check shaves off even more render time.
- Gives you a simple syntax for defining components as functions.
- To render a subcomponent from within another component, you call it as an ordinary function. In Om, you pass your subcomponent function to
om/build. I find Quiescent’s way easier to work with and read (but it’s mostly cosmetic).
- Doesn’t care how you structure or store your state. You just pass it into the components.
shouldComponentUpdatefor you. (Om does this too.)
- You’ll need to render your top-level component manually when your state changes.
shouldComponentUpdatedoesn’t work for everybody (as we’ll see in a moment). Quiescent currently doesn’t allow you to customize or skip the
Right now, Quiescent is my favorite of the bunch for any application where the data model doesn’t fit nicely in a tree struction as Om would like.
Plain ol’ Interop
This isn’t a library at all, this is just React. Using React directly from ClojureScript isn’t difficult, and it’s worth being aware of.
- No abstractions between you and React (and thus, only one set of docs to read).
- Fewer dependencies.
- A bit of boilerplate code, or you’ll need to implement your own function or macro to simplify the sitax of creating a component.
The example for DataScript (a neat project that implements a Datomic-esque database in ClojureScript) uses React directly. It wraps the React interface for convenience, but the code to wrap it is only 42 lines. The UI components are straightforward to read, especially if you’re used to Hiccup syntax.
That example used to use Quiescent, but it stopped because of the
shouldComponentUpdate that Quiescent uses. Comparing entire database states with
clojure.core/= is too slow to be useful.
And that’s it! Do you know of another bridge from React to ClojureScript? Let me know and I’ll write it up.