Mike, thanks for sharing! This helped clarify some of my thoughts and introduced me to a couple of possibilities I hadn't considered. I'd enjoy seeing a demo app if you do decide to publish one.
On Wednesday, October 8, 2014 3:05:06 PM UTC-4, Mike Haney wrote: > 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.
