I've taken a different approach which I think has really worked well, so I 
thought I would share it.

The inspiration for my approach came from this article 
http://tonsky.me/blog/datascript-chat, in which he uses Datascript, core.async, 
and uses React directly for rendering (using Sablano for templating).  I've 
adapted this basic approach with a few changes:

1) Using React directly is fine, although you are relying solely on React's DOM 
differencing for performance, with no IShouldUpdate optimizations like you get 
with Om/Reagent.  To be honest, that's probably fine for most apps, but I 
really like Reagent and wanted to simplify my templates a bit, so I combined 
the Datascript approach with Dave Dixon's binding code (here: 
https://gist.github.com/allgress/11348685).  This has worked fantastically!

So instead of monitoring the transaction stream and re-rendering the UI on 
every change (as in the original article), individual Reagent components can 
bind a datalog query to a reagent/atom and it will be updated whenever that 
data changes in Datascript.  This works for arbitrarily complex queries and I 
have been very pleased with the results in practice.

For example, in our app you can select an employee from a dropdown and  then it 
will list work orders for the selected employee - a fairly common use case.  
This was trivial to implement with 3 queries - a query for the selected 
employee, a query for all employees (to show in the dropdown menu) and a query 
for all workorders assigned to the selected employee.  Each Reagent component 
was built with its respective query bound to a r/atom, and that's literally all 
there is to it.  When the selected employee changes, the workorder query is 
automatically re-run and its atom updated, which triggers Reagent to re-render 
the workorder list.

2) So what about transient state - such as whether the dropdown is collapsed or 
not?  This could be represented as component state easily in Reagent (or Om, 
for that matter), but that brings up other issues.  For example, when a 
dropdown menu is expanded, it's usually expected that clicking outside of the 
menu will collapse it (we've all seen those annoying sites where you can only 
close the menu by clicking on it again - UX Fail!).  

This is tricky to do with local state and it's easy to couple components that 
really have no business knowing about each other.  Well thought out event 
pipelines with core.async can solve this, but I try to be lazy whenever 
possible.  So this transient state is kept in the DB as well.

There are a couple of ways to do this.  The easiest is to have a single "UI 
state" entity and just lump all the transient state in there, and any component 
needing access to it can just bind a r/atom to it.  I created a bind-entity 
variant of the binding code to do just that - bind a r/atom to a single entity 
and update whenever it changes.  What I ended up using is a more granular 
approach, where each component can create its own entity to track transient 
state and get a bound r/atom representing that state.  That was easy to setup 
with a few helper functions.

3) I'm using plain functions instead of core.async for event handling.  Just a 
personal preference.  IMO, since the DB is the single source of authority and 
any component can easily bind to the DB and react to changes, I found that 
pub/sub was just unnecessary complexity (it could easily be added back if I 
find I need it, though).

4) I wasn't comfortable with having the Datascript DB (and core.async channels, 
which I'm no longer using) as global state, so I created a quasi component 
system for my app.  (NOTE to Stuart Sierra - PLEASE port component to 
Clojurescript!  If you don't, I may just have to tackle it myself soon).  This 
adds some nice structure to app and is generally OK, but to be honest I'm still 
not 100% convinced the additional complexity is worth it, at least for smaller 
apps.  The main reason I stuck with it is because although this current app is 
small-medium in size, and I could get away with not structuring the code, then 
next phase of this project will entail building a significantly larger app 
(hopefully reusing many of the components from this app).

I still have mixed feelings about that last one.  Just having a global DB and 
event bus that everything accesses certainly makes things MUCH easier, and 
global state seems to be at least overlooked if not accepted in the Javascript 
world.  But it just feels icky to me - 20+ years of professional experience 
tells me that shortcuts like that come back to bite you in the a** more often 
than not.  I would be interested in hearing others' opinions - maybe I'm just 
being too "old school"?

Well hopefully someone finds this approach interesting.  Whether or not you use 
core.async/functions or components/global-state, the basic approach of 
Datascript + Reagent + bound queries is a BIG win in my book, and I've been 
very happy with the results.  If people are interested, I've thought about 
pulling out the key pieces into a demo app (minus the proprietary stuff for my 
project), but it will probably be at least a couple weeks before I could get to 
that.  In the meantime, if anyone has specific questions I can try to answer 
and maybe pull out a few snippets as gists.

-- 
Note that posts from new members are moderated - please be patient with your 
first post.
--- 
You received this message because you are subscribed to the Google Groups 
"ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/clojurescript.

Reply via email to