Om and Reagent

I’ve been play­ing, of late, with Clo­jure­Script fron­t-ends, specif­i­cally with Om and with Reagent. Between the two, I like Reagent much bet­ter. The short rea­son why is that it feels much more ‘Clo­jur­ish’ and the pro­gram­ming model feels much more acces­si­ble, espe­cially to some­one already famil­iar with Clo­jure/­Clo­jure­Script. Om, by con­trast, feels like a thin­ner wrap­per over React And even though it does a num­ber of neat things, it’s ulti­mately more unwieldy. 1

Both of these frame­works are basi­cally wrap­pers for Reactjs which is one of the more unique Javascript front end frame­works out there. The quick low-­down on React is that it is a tool for gen­er­at­ing a UI (ba­si­cal­ly, your HTML DOM), from appli­ca­tion data. Instead of attempt­ing to mutate a sta­tic DOM in-­place, React effec­tively regen­er­ates the DOM from scratch with every update to the appli­ca­tion data. React has a num­ber of tricks to make this per­for­mant, significantly: cre­at­ing a stripped down vir­tual DOM which the appli­ca­tion code oper­ates on instead of the browser’s actual DOM. Oper­at­ing on the VDOM is quicker than oper­at­ing on the browser’s native DOM and it can be diffed with older ver­sions to allow React to selec­tively update the browser’s DOM. The end result is that using React involves defin­ing a lot of objects called ‘com­po­nents’ which gen­er­ate HTML from plain Javascript data. This means you can sep­a­rate your appli­ca­tion log­ic, which gen­er­ates and oper­ates on the appli­ca­tion state from your UI layer which is merely passed the appli­ca­tion state (or a part of it.) This makes it rather func­tional in con­cep­tion, mak­ing it a good match for Clo­jure­Script, which is strictly functional.

Reagent and Om are two dif­fer­ent libraries which take two dif­fer­ent approaches to inte­grat­ing React into Clo­jure­Script. Of the two, Om is prob­a­bly the best well known. It was cre­ated by the very pro­lific David Nolan who is one of Clo­jure­Scrip­t’s main­tain­ers and this means that Om has a pretty good pedi­gree. Gen­er­al­ly, Om encour­ages plac­ing the appli­ca­tion state in a sin­gle atom. One can then define com­po­nents by using reify to imple­ment a pro­to­col. Com­po­nents can main­tain a local state and com­mu­ni­ca­tion between them is usu­ally man­aged using asyn­chro­nous code, for exam­ple using core.async. Com­po­nents, along with the root atom are passed to the root func­tion, which mounts the com­po­nent. The atom is then watched so that changes to it cause com­po­nents to update parts of the UI. There are a num­ber of exten­sions for Om, such as om-tools which helps to dis­guise some of the inter­face warts and Sablono which allow one to use Hiccup style tem­plates instead of the func­tion calls. There is also an array of pre-­made com­po­nents ready for inclu­sion in an Om based appli­ca­tion.

Reagent, 2 on the other hand, is built around its own ver­sion of atom which uses ‘computed observables’ to keep the UI up to date. Every change to one of these atoms results in an update of the com­po­nents that depend on it. Com­po­nents are defined using nor­mal Clo­jure­script func­tions. These func­tions must either return a Hic­cup-­like data struc­ture or return another func­tion, which in turn returns a Hic­cup-­like data struc­ture. Those func­tions are ulti­mately passed to Reagent which does the hard work of con­vert­ing them into com­po­nents. There is less empha­sis on local state in Reagent than in Om and the default mode for com­mu­ni­ca­tion between com­po­nents is to sim­ply have top level com­po­nents pass argu­ments to lower level com­po­nents. The big dif­fer­ence between Reagent and Om is that while the later pri­mar­ily exposes Reac­t’s API and life­cy­cle events, the for­mer wraps them with a func­tional and very Clo­jur­ish inter­face. This means that Reagent feels more nat­ural to an expe­ri­enced Clo­jure devel­op­er.

Now, Om has some neat ideas baked into it. Using Cursors to allow com­po­nents to selec­tively update them­selves for exam­ple, is pretty cool. But it also has some ugly bits. The basic syn­tax, for exam­ple feels rather com­plex. Using reify, pro­to­cols, and #js does­n’t feel very Clo­jur­ish. Om-­tools helps with this but we’re still left with the under­ly­ing prob­lem that defin­ing com­po­nents in Om is more like imple­ment­ing an object than writ­ing a func­tion. This has value in that it more directly exposes Reac­t’s life­cy­cle, which, for exam­ple allows users to access hooks are mounted for the first time, but it feels wrong. It’s worth not­ing that while Reagent does­n’t work by default by directly expos­ing the Reac­t’s life­cy­cle meth­ods, they can still be accessed by defin­ing meta­data on com­po­nents.

Of more con­cern are spe­cial restric­tions that sur­round defin­ing com­po­nents in Om. The func­tions which return rei­fied com­po­nents have to be idem­po­tent. Idem­po­tency is a use­ful fea­ture, but requir­ing users to write idem­po­tent func­tions strongly vio­lates the prin­ci­ple of least sur­prise. This means that if you want to use local state with an Om com­po­nent, you can’t sim­ply use a clo­sure to wrap the com­po­nent in a let bind­ing. Instead, Om pro­vides it’s own com­po­nent state seman­tics. Reagent has the same prob­lem but it resolves it by allow­ing com­po­nent func­tions to return other func­tions, which can then be wrapped in clo­sures. This feels a lot sim­pler and more intu­itive.

Not that that mat­ters too much, as I’ve found I don’t actu­ally care too much for using local state in com­po­nents any­way. Om seems to take a strongly opin­ion­ated approach that appli­ca­tion data should exist in one global atom, which tran­sient state should be com­po­nent local. How­ev­er, I’ve found it’s not always obvi­ous to which com­po­nent state is really local. If you have a ta­ble with one item ‘se­lect­ed’, is the ‘se­lect­ed’ state part of the ta­ble row or the ta­ble as a whole? What if you have other UI com­po­nents that aren’t part of the ta­ble at all but whose appear­ance depend on which row is select­ed? Allow­ing com­po­nents to read the local state of their par­ent com­po­nents is a bear and I don’t even want to think about try­ing to syn­chro­nize non-child-­par­ent com­po­nents. Just throw­ing that kind of state on a root level atom and using other lan­guage fea­tures to par­ti­tion code and data log­i­cally seems eas­ier to me. It’s pos­si­ble that I’ll suf­fer for my hubris in the future. We shall see.

One final thing is using a Hic­cup style data struc­ture to con­struct com­po­nents gives Reagent an extra advan­tage. Gen­er­at­ing or mod­i­fy­ing Reagent com­po­nents is just a mat­ter of manip­u­lat­ing this data struc­ture. So, if one wanted to mod­ify the out­put of a third party library before it was passed to Reagent, you could do so. I don’t think this is pos­si­ble with Om com­po­nents which gen­er­ate React VDOM direct­ly. For this rea­son, Reagent com­po­nents are sub­ject to a kind of metapro­gram­ming that Om com­po­nents are not. This is very cool.

Ulti­mately my analy­sis should be taken with a grain of salt. I’m com­ing at this from the stand­point of a devel­oper fam­i­lier with Clo­jure but less so with Javascript or React. It should­n’t sur­prise any­one that the more ‘Clo­jur­ish’ solu­tion would appeal to me more. I could just be miss­ing some­thing sub­tle whereby Om’s cur­sors, direct access to lif­ey­cle meth­ods and empha­sis on fully decou­pled com­po­nents ren­ders it more suit­able to com­plex tasks. I can’t see that right now, but I may change my mind with more expe­ri­ence. Time will tell.

  1. I haven’t tried the other ClojureScript React wrapper, Quiescent. It looks promising, leaving the question of state management to developer entirely, unlike either Reagent or Om. I’ll have to take a real look at it sometime. 
  2. Formerly known as Cloact; The name was changed on account of the similarity to ‘Cloaca’. A good change, I think 

Last update: 14/01/2015

blog comments powered by Disqus