Nothing Undone

A weblog, by Peter Jaros.

React Four Ways: How to Use React in ClojureScript, and Why.

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?

Manipulating the DOM using JavaScript has always been tricky, and mostly the problems come down to this: the DOM is stateful. Really, really stateful. Take the famous TodoMVC application for example, a demo application that’s been implemented in several different frameworks to show off their differences.

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.

Enter React

Here’s the big idea in React: re-render the entire page, but don’t create real DOM elements. Instead, create JavaScript objects which represent the DOM you want, then compare it to the actual DOM, and only change the DOM where it’s different. You’re only touching the DOM exactly as much as you have to, but you get the convenience of specifying your DOM functionally.

By functionally, I mean that your DOM is now literally a function of your data. To illustrate, have a look at the render function from the React.js version of TodoMVC. (You’ll notice that there appears to be HTML all over this “JavaScript” code. That’s a convenience. This is a JSX file, which gets compiled to actual JavaScript. In the process, those HTML tags are converted into code that creates and returns those DOM objects. But not real DOM elements, remember, just React DOM objects.) What that function returns, at the end, is the DOM structure (or rather, a representation of it) for the entire TodoMVC application. At the end of the script, we render it to the actual DOM, and request that it re-render every time the model changes.

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

(TodoMVC Demo)

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
(def app-state {:todos [{:title "Get grocieries"
                         :completed true}
                        {:title "Sweep floor"
                         :completed true}
                        {:title "Respond to email"
                         :completed true}
                        {:title "Blog about React"
                         :completed false}]})

;; Now let's say we're inside a component, and Om has given
;; us a cursor on the app-state called app-state-cursor.

(let [todos  (:todos app-state-cursor)  ; This is a subcursor.
      a-todo (nth todos 3)]             ; So is this.
  (om/transact! a-todo :completed not))

;; That says: toggle (using `not`) the value of the completed
;; key at a-todo by `swap!`ing the value of the `app-state`
;; atom it's derived from. In other words, it's the same as:

(swap! app-state #(update-in % [:todos 3 :completed] not))

;; The difference is that we didn't need to remember the path.

So cursors are pretty great. The Cursors page from the Om wiki has a good deal more to say about them.

Advantages:

  • 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.

Disadvantages:

  • 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.

Reagent

(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

(TodoMVC demo)

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.

Advantages:

  • 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.
  • Provides shouldComponentUpdate for you. (Om does this too.)

Disadvantages:

  • You’ll need to render your top-level component manually when your state changes.
  • Quiescent’s shouldComponentUpdate doesn’t work for everybody (as we’ll see in a moment). Quiescent currently doesn’t allow you to customize or skip the shouldComponentUpdate check.

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.

Advantages:

  • No abstractions between you and React (and thus, only one set of docs to read).
  • Fewer dependencies.

Disadvantages:

  • 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.

Bon appétit!

And that’s it! Do you know of another bridge from React to ClojureScript? Let me know and I’ll write it up.

A Distraction-Free Presentation Viewing Environment

I’ve just watched Rich Hickey’s talk Simple Made Easy, which has been waiting in my queue for a very long time. It’s a brilliant talk. Watch it.

Unfortunately, we won’t be able to watch it for Bentoconf. InfoQ, which hosts the talk, has a fancy playback system which syncs a video feed of just the presenter with a separate slide display, rather than editing the two together into a single video. Thus, we can’t play it through Synchtube, which powers Bentoconf. That’s a shame.

It also means that you can’t full-screen the talk. That is, you can full-screen the presenter, but then you can’t see the slides. That’s no good. And just full-screening the browser window leaves something to be desired. The chrome of the page is bright, messy, and distracting. I’m here to watch a talk, thank you.

Luckily, userstyles.

Presentation with userstyle applied

Process Substitution: The Coolest Shell Trick You Didn’t Know

The syntax in the blog post is compatible with bash and zsh. With other shells, YMMV.

Well, maybe you know about it. I didn’t. It amazes me that after all this time the shell can still surprise me.

You probably know about the man command. I sure hope you do. Give it a topic, and it’ll tell you all about it. The topics are usually commands. For instance, ssh is a fairly complex and full-featured command. I wonder how it works. Let’s find out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ man ssh
SSH(1)                    BSD General Commands Manual                   SSH(1)

NAME
     ssh -- OpenSSH SSH client (remote login program)

SYNOPSIS
     ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
         [-D [bind_address:]port] [-e escape_char] [-F configfile] [-I pkcs11]
         [-i identity_file] [-L [bind_address:]port:host:hostport] [-l login_name]
         [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
         [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port]
         [-w local_tun[:remote_tun]] [user@]hostname [command]

DESCRIPTION
     ssh (SSH client) is a program for logging into a remote machine and for executing com-
     mands on a remote machine.  It is intended to replace rlogin and rsh, and provide secure
     encrypted communications between two untrusted hosts over an insecure network.  X11 con-
     nections and arbitrary TCP ports can also be forwarded over the secure channel.

Awesome, everything we need to know about ssh. But sometimes you don’t know what command to look up. Maybe we just know that we want to log in remotely. How would we find the right command for that?

Enter whatis:

1
2
3
4
$ whatis "remote login"
rlogin(1)                - remote login
rlogind(8)               - remote login server
ssh(1)                   - OpenSSH SSH client (remote login program)

Cool, we even see that there’s an older remote login system we can look into.

That’s cool, but what’s this process substitution thing?

Okay, okay. I knew about whatis. I also knew about a command called apropos. In fact, my understanding the two was the same; I wasn’t sure what the difference was. They both searched for man pages based on keywords. So I looked at man whatis and man apropos. Very similar entries. (Go ahead, take a look for yourself.)

I wondered, is there a good way to see the differences between these pages? Well, diff should do that for us. But diff wants to take two files. We can pipe text into it, but that can only serve as one of the files; you can’t pipe two things into the same program.

Or can you?

Benchmarking My Dotfiles.

I’m a huge fan of Gary Bernhardt’s Destroy All Software. If you’re not a subscriber, I highly recommend it. It’s $9 per month. Pay the $9, watch as many videos as you can in a month, and see if it’s not worth staying subscribed.

The things Gary teaches explicitly are wonderful, but there’s a lot to be learned implicitly from watching his screencasts. His patterns of working with Vim and editing files are often quite different from mine. For instance, Gary often opens Vim to edit a single file, closes it to get back to the shell, and reopens it to edit another file. I rarely do that. I keep Vim alive as long as possible, like an IDE. Why is that?

Because my Vim takes ages to boot up.

Let’s Not Teach the Fundamentals First.

This weekend I TAed at RailsBridge Boston. RailsBridge is a day-long Ruby on Rails workshop for women of all backgrounds. It is awesome.

I can’t express the joy that comes from seeing the lightbulbs go off in someone’s head for the first time as they learn to program. The magic in the moment when they become visibly excited at the prospect of what they’re doing.

“So, "Hello, world" doesn’t do anything on its own, but if I put puts in front of it, I see Hello, world in the Terminal. Can I put something else after puts? Why is it in double-quotes? Will single-quotes work? What about no quotes?”

So many amazing questions. So much beautiful, exciting learning. To be honest, I’m not sure I enjoy programming nearly so much as I enjoy learning to program, and helping someone else find that joy is just as fun.

While I was there, I began to crystalize a theory of how we learn to program, and it goes something like this: You can’t teach someone to program. We each need to learn it for ourselves. All you can do is make the right information available at the right time. It’s like a gardener fertilizing a flowerbed. You can’t make the flower. That’s the seed’s job. Your job is to provide the right conditions.

Toward a Culture of Pairing

I learn best when I work directly with other people.

Books are great. Talks are great. Screencasts are great. I encourage everyone to take advantage of the one-way, work-at-your-own-pace educational resources available, because in the world of software development there are a lot of awesome ones.1

But there’s something those lessons can never give you.

In Zen, there is a notion of Dharma transmission. Dharma, in Buddhism, is the teaching of the Buddha. It is Buddhist wisdom, and it is revered as a holy thing. It is improper to place a book containing Dharma teaching on the ground; instead it is placed on a cushion.

Zen strains of Buddhism tend to take pride in the source of their teachings, tracing their lineage back to the Buddha. To be accepted into this lineage is to receive Dharma transmission. It is not only the learning of facts and sayings; in fact it is not truly those things at all. What is truly transmitted as Dharma is something more abstract. It is akin to a flame passing from one candle to another.

How My Mac Became Horribly Fragmented, and How I Fixed It.

A tale of caution:

Sometime last year, long after most everyone else, I finally upgraded my Mac to Lion. And yes, I had to get used to the scrolling, and the buttons looking different, and fullscreen working differently, but I got used to all of that. There’s one thing I didn’t get used to, though.

My Mac got really, really slow.

Usually I noticed it when I switched applications, but I also noticed it any time I used the disk a lot or swapped out a lot of memory. I was worried my disk was dying.

I asked folks on Superuser.com, once about why my computer was sluggish, and once about why my menu bar blacked out the application name when I switched applications and looked like this:

Menu bar with oddly blacked out application name

Let’s Talk About Jobs.

Get ready for hotness in a tiny package. This one’s a habenero.

Sometimes you’re in the terminal, editing a file. Oh, your editor doesn’t run in the terminal? Okay, maybe you’re looking at a man page. Or playing Nethack. Whatever. You’re doing something that’s taking up your terminal, but you want to use the shell to go check something out, like look up a man page or look at a file or run a test.

You could quit, do your thing, and come back. You could open a whole new terminal and get your new shell in the state the old one had. Or maybe the app you’re running will even let you shell out to run something. But I’m going to tell you about something that’s often even neater, something that’s been a part of your shell since perhaps before you knew what a shell was, but which most people I work with have no idea exists.

I’m going to tell you about job control.