re-frame with a large application

1,076 views
Skip to first unread message

Michael Campagnaro

unread,
Mar 16, 2015, 3:53:59 PM3/16/15
to clojur...@googlegroups.com
First off, re-frame looks really great, Mike. Thanks for writing it.

My team has been working on a Reagent SPA for half a year and it's our first CLJS project so we are feeling some pain due to design choices that were not the best. We really like the re-frame pattern and want to transition over to it. I'm attempting to work out a plan for this. Either creating a new app on the side and porting code over to use re-frame or gutting the current app in place.

Since re-frame is so new it's hard to tell what the limitations are. On one hand the codebase is small, simple and nothing stands out as being a potential problem in a large app. On the other hand I can't help but be skeptical and curious :)

So I'd love to hear about anyone's experience using re-frame to power a non-trivial app, preferably in production. Are there things that I should be mindful of when scaling the re-frame pattern?

platon...@gmail.com

unread,
Mar 17, 2015, 10:31:25 AM3/17/15
to clojur...@googlegroups.com
We've been using a very similar approach to re-frame in production for almost a year now (with Om instead of Reagent). The app isn't huge, but it's a couple thousand lines of cljs. The main difference with re-frame is that we allow components to subscribe to events in order to manage their local state as we don't keep everything in the app state.

The app is split into a namespace group for each module of functionality, with each group having its own handlers for application events, remote endpoints and components.

There is one state atom for the whole application, although every module gets it's own state under a separate key in the state map. There is some reference data common to every module which is the only part accessed by everyone (using the Om ref-cursors).

I feel that the re-frame architecture scales very well if you are consistent in naming and separating the responsibilities.

Marc Fawzi

unread,
Mar 17, 2015, 11:27:30 AM3/17/15
to clojur...@googlegroups.com
<<
The main difference with re-frame is that we allow components to subscribe to events in order to manage their local state as we don't keep everything in the app state.
>>

That's very cool if what you mean is storing state that no other part of the app needs to know about, like the keys being entered into an input field etc

<<
There is some reference data common to every module which is the only part accessed by everyone (using the Om ref-cursors).
>>

Can you give examples? Let's say I have two components that use the same derived or raw data, I would put that data outside of app state, into a data store/cache/indexeddb/etc






--
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 clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

platon...@gmail.com

unread,
Mar 17, 2015, 2:28:10 PM3/17/15
to clojur...@googlegroups.com
On Tuesday, March 17, 2015 at 4:27:30 PM UTC+1, marc fawzi wrote:
> <<
> The main difference with re-frame is that we allow components to subscribe to events in order to manage their local state as we don't keep everything in the app state.
>
> >>
>
>
> That's very cool if what you mean is storing state that no other part of the app needs to know about, like the keys being entered into an input field etc

Yes, that's the main reason. Also things like `loading?/updating?` states to represent the progress of async actions and other similar cases.

>
>
> <<
> There is some reference data common to every module which is the only part accessed by everyone (using the Om ref-cursors).
>
> >>
>
>
> Can you give examples? Let's say I have two components that use the same derived or raw data, I would put that data outside of app state, into a data store/cache/indexeddb/etc

That's also possible. We keep that data in the `:reference` part of the app atom. In our case it's mostly used for common lists of values, so nothing spectacular.

Mike Haney

unread,
Mar 17, 2015, 10:09:12 PM3/17/15
to clojur...@googlegroups.com
In my experience, using component local state seems harmless enough in the beginning, but I almost always find a need to move it to global state as an app matures. A few common examples:

- text input: local state works great until you start to add validation, then it is usually better to have it in global state. Also, clearing form fields on submission is easier to handle with global state.

- loading indicators: local state works fine, until you start handling network errors and have to clear the loading indicator when there's an error.

- collapsed/hidden flags: for drop down menus, nav drawers, etc. Again, it's easier to use global state so you can do things like close the menu when the user clicks away from it (a pet peeve of mine are menus that can only be closed by clicking on the menu itself - argh!).

I'm finding that most of these things become ridiculously easy with re-frame. Simple example - create a "menu-closing" handler middleware and attach it to any handlers where you want the menu to close.

Marc Fawzi

unread,
Mar 17, 2015, 11:03:51 PM3/17/15
to clojur...@googlegroups.com
That's insightful, but let me poke at these statements anyways, and please correct me where I'm wrong... an exercise in learning:

<<
- text input: local state works great until you start to add validation, then it is usually better to have it in global state.  Also, clearing form fields on submission is easier to handle with global state.
>>

I don't save to global state the transient state of each component, only the state of the entire app at the time I'm transitioning the app's sate, e.g. submitting a query. That final state goes into global state. Local state for me, being new to React so forgive me if I'm doing it wrong, is a way for me to tell React to render my keystrokes. I don't need to store those keystrokes in global state until the user hits submit. Then I validate etc.

<<
- loading indicators: local state works fine, until you start handling network errors and have to clear the loading indicator when there's an error.
>>

Right. I see what you mean, but the loading indicator's local state is accessible to me and can be reset when I detect a network error. In what situations would it be not accessible? (React question)

<<
- collapsed/hidden flags: for drop down menus, nav drawers, etc.  Again, it's easier to use global state so you can do things like close the menu when the user clicks away from it (a pet peeve of mine are menus that can only be closed by clicking on the menu itself - argh!).
>>

Oh again, this is pointing to an issue with accessing a component's local state from outside the component. Is this an issue in React?


<<
I'm finding that most of these things become ridiculously easy with re-frame.  Simple example - create a "menu-closing" handler middleware and attach it to any handlers where you want the menu to close.
>>

I've been reading up on re-frame and it's not a foreign concept to me but I need to think a lot more about the details or missing details. It sounds like it's got some powerful patterns bundled together.


Mike Thompson

unread,
Mar 17, 2015, 11:46:30 PM3/17/15
to clojur...@googlegroups.com
Yes, indeed. Synchronizing state is a fraught process. Keep it in the one place if you can. http://martinfowler.com/bliki/TwoHardThings.html

Marc Fawzi

unread,
Mar 18, 2015, 12:24:07 AM3/18/15
to clojur...@googlegroups.com
But there is transient state and state that you care to keep.

I'm not sure why you would want to put transient state like keyboard input into global state before you actually need to transition app state. At that point grab the input component state and put it in global state, then transition, and now you can undo and redo.


Mike Haney

unread,
Mar 18, 2015, 12:54:19 AM3/18/15
to clojur...@googlegroups.com
Text input is one of the cases where local state can make sense, I was just pointing out some of the problems you can run into. Like if you need to validate as the user is typing instead of when you submit the form.

If undo/redo is a requirement, then you have a valid point. Personally, I think that aspect of React has been oversold. There are some apps where it makes a lot of sense and is easy to do, like that drawing program that uses Om (Goya, I think that's the name). In many/most apps, it either doesn't make sense or it adds significant complexity. Certainly any app syncing with a back end service will require more than just swapping out global state to support undo.

Mike Thompson

unread,
Mar 18, 2015, 1:17:03 AM3/18/15
to clojur...@googlegroups.com
Sceptical and curious is the only sane mindset!! Me too.

I'm reminded of Chou En Lai's comment in 1972: he was asked what he thought was the historic impact of the French Revolution. He considered the question and replied: "It's too soon to tell."

We have developed three apps with re-frame. Two small ones and one larger. By larger, I mean many panels (dynamic arrangement of them under user control), a few non-trivial popups (dialog like), sophisticated undo, drag 'n drop, showing warnings, going into and out of consequential error states, some limited interaction with a server, etc. So far it has been as Mike Haney said "ridiculously easy" which is the ultimate compliment. BUT we also tend to develop slightly atypical web apps - they tend to be very desktop-app-ish, so our experience might not generalise.

Be aware that I'm also a relative browser-tech neophyte. I have decades of experience in other tech (Flash/Flex, QT, MFC, Interviews, python, C++, q, k, Smalltalk, etc), but I've only been in the HTM5 world for 18 months (although I do seem to remember dabbling with "DHTML" in 1998 :-)). So I'm still learning.

Problems:
- drag/drop was a pain, but that was more to do with the hideous HTML5 API, interacting with badly with reagent/react, I did solve it in the end. Easy next time.
- the re-frame testing story is not yet complete (but it looks likely to turn out well, I think)
- we still don't have a good enough story for animations (but equally we haven't properly focused on it yet).
- getting the more complicated subscriptions correct can be "too subtle". I'm wrestling with a design tweak which will make this easier (less subtle).

I'm also cautious about how we are going to integrate with js libraries like D3. Might be easy or hard, haven't tried.


--
Mike

platon...@gmail.com

unread,
Mar 18, 2015, 3:02:45 AM3/18/15
to clojur...@googlegroups.com
We solve all of these problems by having components subscribe to global events and using React mixins (with om-tools). This way global state is not polluted with all of the minutiae.

For example, we implement the loading indicators by mixing in the "loadable" mixin and handling the whatever actions trigger the updates/loading. This way when an async action gets initiated, the component gets an "[:initiated :action]" event and e.g. draws an overlay with a spinner over itself. When the action completes, the component handles a "[:completed :action]" event with a success/error payload. This way the handling of error/success conditions only useful to the component in question stays with the component and reusable pieces get abstracted away.

I'd say our approach is isomorphic to re-frame in this regard.

Colin Yates

unread,
Mar 18, 2015, 6:18:16 AM3/18/15
to clojur...@googlegroups.com
Not to go too far down the 'communicate with local state' tangent, but
isn't a global event-bus (eg. a (chan)) sufficient? I use om and
app-state for all state (except projections) and haven't found any of
the given scenarios a challenge.

For anyone deciding about whether to jump on board BTW; the two Mikes
are awesome and give this a _lot_ of credibility. If this had been
around last year I think I would have bitten your hand off Mike ;).
Don't get me wrong, David and Om are fantastic, but most of the 'how
do I do this' community in Om or React has been from a small number of
people, including Mike.

This really feels like a solid, well engineered library which allows
me to build solid, well engineered applications being opinionated
enough so I don't go off track but flexible enough not to lock me in.
And the fact MikeT has done a non-trivial app means a lot to me.

Great work, and keep up the excellent community.

(that's enough sycophantic nonsense from me).

Mike Thompson

unread,
Mar 18, 2015, 7:58:14 AM3/18/15
to clojur...@googlegroups.com
On Wednesday, March 18, 2015 at 9:18:16 PM UTC+11, Colin Yates wrote:
> Not to go too far down the 'communicate with local state' tangent, but
> isn't a global event-bus (eg. a (chan)) sufficient? I use om and
> app-state for all state (except projections) and haven't found any of
> the given scenarios a challenge.


A global event bus is certainly a popular solution when you have distributed state and control, and you want the parts coordinated.

PureMVC and many other OO frameworks take this approach. Sounds like some have taken this approach with OM too when coordinating components. It certainly works!

With re-frame I tried to not have that problem in the first place (so then I didn't need a solution for it).

--
Mike

platon...@gmail.com

unread,
Mar 18, 2015, 9:46:45 AM3/18/15
to clojur...@googlegroups.com
Not sure I agree that re-frame escapes the distributed state and control parts better than other discussed approaches, but I think we've scared the OP enough already :)

I believe the consensus for the original question is that re-frame is great for large applications. So go and create!

While we may continue bickering over the details in another thread :)

Michael Campagnaro

unread,
Mar 18, 2015, 6:14:42 PM3/18/15
to clojur...@googlegroups.com
Heh, I'm not scared, just really busy with work so I haven't had a chance to reply yet!

Thanks for the input everyone. I'm loving the discussion that has broken out here and please do continue if you feel so inclined.

In my current app we are using a global event bus and components maintain their own local state. It's been OK for the most part. I think I just need to start building something with re-frame and think on the various ways you guys are managing state :)

Mike Haney

unread,
Mar 19, 2015, 9:07:28 AM3/19/15
to clojur...@googlegroups.com
You might want to check out this article: https://github.com/Day8/re-frame/wiki/Alternative-dispatch%2C-routing-%26-handling

Using techniques MikeT describes there, it should be possible to slowly migrate your app to re-frame rather than undertake a rewrite. For instance, it should be possible to modify re-frame to forward any events it doesn't know about to your existing event bus. This would allow you to migrate to re-frame's event handlers piece by piece.

In fact, that would probably be a useful enhancement in general - the ability to register a "default" handler that gets called for any event that doesn't have a registered handler. I'll add that to the issues and we can discuss it further there.
Reply all
Reply to author
Forward
0 new messages