Clarity questions on reagent

208 views
Skip to first unread message

Colin Yates

unread,
Dec 11, 2014, 5:26:24 AM12/11/14
to clojur...@googlegroups.com
Hi all, having written a non-trivial app in om I am thinking of prototyping it in reagent. Before doing that I want to clarify a few things.

Some relevant background:
- the app has multiple pages, but only one page is in the dom at any one time (i.e. I am not hiding anything it simply isn't rendered)
- a page has multiple components only some of which will be in the dom at any one time (as before, nothing is hidden it simply isn't rendered)
- when switching between pages the previous state of the page should be retained. This means storing which components are visible and scrolling positions etc. in the app-state
- communication happens over async channels which are managed in IWillMount and IWillUnmount

om's common approach (although it isn't enforced by om) of everything in one atom works well here as the lifecycle of data isn't tied to the lifecycle of the component. om's excellent cursor mechanism goes a long way to allowing component rendering lifecycle to be restricted to its little bit of the global app-state which has significant performance benefits.

In reagent, I understand there isn't such a thing as global state (although of course you could go down that route), rather each component/component hierarchy gets their own atom.

My questions are:
- how to handle the state of non-rendered components. Am I going to end up with an om-like single atom (albeit per page)? Without om's cursors (I believe reagent's cursors are clever syntatic sugar and don't contribute to efficiency - is that right?) isn't this going to be very inefficient?
- where are channels typically configured in reagent? Am I going to end up r/create-class or r/with-meta everywhere?

To be blunt, I see lots of people move away from om to reagent, but from where I sit (in ignorance of reagent) it doesn't seem a win in this situation. But I know I'm not doing anything special - these are all happy cases so I must be missing something.

So, rather than recreate an 'om-style app' in reagent and fight reagent's selling points (e.g. localised state and low-ceremony) I thought I would ask how does reagent solve these problems.

Thanks all :).

Mike Haney

unread,
Dec 11, 2014, 4:15:04 PM12/11/14
to clojur...@googlegroups.com
There's nothing wrong or inefficient with using a single atom for your state in Reagent. I do that more often than not, and it works fine. As I understand it, the Reagent model is this: when you deref an atom, that is Reagent's cue to re-render any components that reference that atom (actually it schedules a render for the next animation frame, just like Om). Within a component, Reagent caches the props for each child component and will only re-render the children whose values have changed. So in a scenario where you have a single atom, and let's assume every component references it, then yes, every component could potentially be re-rendered but the caching prevents any extra rendering happening at the React level.

So, although the exact mechanism used is different between Om and Reagent, the end result is the same - they both use the power of immutable data structures to do simple equality checks for Reacts should-update method. One nice thing about the Reagent model is that you still have the option of splitting your state up if you want, either to control the scope of re-renders or just simply because that model fits your app better. It's also important to remember that React itself is very fast, even without these optimizations - by far your biggest gains come from React's diffing to minimize DOM updates, the rest is just icing on the cake.

With regards to state management, I still see 2 big advantages to the Reagent model. First is that core.async is just not needed like it is with Om. If you need core.async for other parts of your app, fine - I still use it for Ajax calls and all those other things where it's wonderful, but there's no need to rely on it for inter-component communication like you usually end up doing with Om.

Second, I really like how local state is also just an atom. This makes it so much easier to reason about your code - no more need to track "is this a cursor or a value" or "am I in the render phase or not". It also makes it easier to write supporting functions and compose them (in Om, start composing functions that take cursors and then if you need to use them outside the render phase - oops!).

As for the specific question about where to create core.async channels (if you can't or don't want to eliminate them) - I posted a gist in one of your earlier threads where I showed how I register subscribers in a closure, just like creating local state. This will work in some cases (top level components that exist for the lifetime of the page) but in general it's not a good thing to do because the go blocks are never cleaned up (my mistake, hope I didn't cause problems for anyone who followed that example). The proper way to do that is in will-mount/unmount, just as in Om. In Reagent, you attach lifecycle functions using either create-component or by attaching metadata. The metadata approach isn't as bad as I originally thought - you can generally write nice generic functions to wrap your components and attach metadata, similar to the mix-ins in Om-tools.

Finally, I don't know if you're aware, but Reagent recently moved to a github org at reagent-project, and things have really been moving. Dan did a great job with Reagent, but was too busy to give it the love it deserved and graciously created the org and brought in more contributors, and the results are already impressive. There are more resources there, including a cookbook and lein template. And the new 5.0 alpha they just released has some nice goodies, my favorite being the ability to use regular React components within your Reagent components. I've also been following closely the conversations between Dmitri, Sean and the other contributors and I like the direction they are headed in (and hope to start contributing myself soon). When I first considered switching to Reagent, my only concern was the relative lack of activity, but that's no longer an issue at all.

Colin Yates

unread,
Dec 11, 2014, 4:20:12 PM12/11/14
to clojur...@googlegroups.com
Mike - thanks again for another fantastic response - thanks for the time.
> --
> Note that posts from new members are moderated - please be patient with your first post.
> ---
> You received this message because you are subscribed to a topic in the Google Groups "ClojureScript" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojurescript/eRjfLnQP2Us/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.
> To post to this group, send email to clojur...@googlegroups.com.
> Visit this group at http://groups.google.com/group/clojurescript.

Mike Haney

unread,
Dec 11, 2014, 4:27:40 PM12/11/14
to clojur...@googlegroups.com
No problem, glad I can help. Feel free ask more questions as you work on your port, either here or on the reagent-project list.

pand...@gmail.com

unread,
Dec 14, 2014, 9:32:27 AM12/14/14
to clojur...@googlegroups.com
Just adding to the description of API differences: with reagent one is able to use clojure's (for ...) to iteratively generate subcomponents, without the need for an additional lib-specific API.

This is a minor convenience but, grouped with the differences Mike listed, the end effect (for me) was that I was able to reason about reagent components without even being familiar with its API, because it's stuff I already knew from clojure and hiccup.

Colin Yates

unread,
Dec 14, 2014, 9:34:08 AM12/14/14
to clojur...@googlegroups.com

Thanks for the info. Sablono goes a long way, but yeah, you still have om/build.

On 14 Dec 2014 14:32, <pand...@gmail.com> wrote:
Just adding to the description of API differences: with reagent one is able to use clojure's (for ...) to iteratively generate subcomponents, without the need for an additional lib-specific API.

This is a minor convenience but, grouped with the differences Mike listed, the end effect (for me) was that I was able to reason about reagent components without even being familiar with its API, because it's stuff I already knew from clojure and hiccup.

Reply all
Reply to author
Forward
0 new messages