Re-Frame: a Reagent Framework for Writing SPAs, in ClojureScript
github.com
github.com
>You know what's good for you, and you know what's right. But it doesn't matter - the wickedness of the temptation is too much.
>The JS world is brimming with shiny component baubles: D3, Google Maps, Chosen, etc.
>But they are salaciously stateful and mutative. And, you, raised in a pure, functional home, with caring, immutable parents, know they are wrong.
It feels like there's a bit of a culture in the Clojure community of making what might have been dry documentation whimsical and fun.
There are two areas that still can be improved in my opinion:
* the asset story. Right now I use a gulp task that prepares my assets and outputs CSS into a folder known to Figwheel, which makes hotreloading work automagically. However, if you need a complicated interaction between JS, HTML and CSS (i.e. you use CSS Modules), it can be a bit more involved.
* the NPM deps story. AFAIK it's gonna be solved in CLJS core soon, but right now your best bet is to pack all NPM deps using native JS tool (webpack, rollup or w/e) into one file and include it before CLJS-generated JS code. Ah, and in some cases you will need externs, too. It's actually way less complicated then it sounds, and if you need any help, feel free to reach me through mail (my nickname here at gmail.com). I would be happy to help.
>to "use something in anger" is a phrase, meaning to use something "for real" - in production, etc. rather than just to try it out.
1) http://david.heinemeierhansson.com/posts/39-im-an-r-rated-in...
For example: "I've been tinkering with emacs, but I've yet to use it in anger."
That said, I would extend the currently en vogue advice re: React/Redux -- don't reach for Redux by default if it's your first go-round -- here.
We have an app in production which at this point is fair to call medium-sized, and have yet had no real pain from using plain Reagent (we do keep all state modifying fns in their own namespace, but basically we just bang on the state atom and it Just Works)
It's not a criticism, and I'm not a react programmer btw, just curious.
Re-frame is a flux-like, elm-like uni-directional data flow framework that uses React as its rendering component. What re-frame gives you is a message bus to which you can dispatch messages from DOM events (eg user clicks button and you dispatch a user-defined message), a handler registration system which allows you to register functions to process messages[1], and a subscription system which allows you to "subscribe" to some view over your applications state in a way that if this view changes, your react components get fed new data and get rerendered (if necessary). Re-frame also has mechanisms to facilitate effectual handlers (that is, handlers which effect the outside world rather than being a pure function of (app state, message)->new app state; like normal handlers are).
That is, react does rendering, re-frame uses react and is a complete application framework that makes it easier to build large complex applications by allowing you to split your application into smaller (mostly pure-functional) pieces that communicate through the messaging system.
[1] Message handlers take in the message and the current application state and return a new version of the application state. That is, they are pure functions that move the application to its next state. Handlers are quite powerful and support "interceptors" (decorators/middleware for messages basically) and there are effect and coeffect handlers for managing side-effects (like dispatching new messages in a feedback loop, or talking to a server, for example).
Let's say I want to write a reusable datepicker component which sends an event like [:date [2017 1 16]] when the user clicks on that date. How can it send it to the calling component? The re-frame pub/sub message bus seems to be global, but what if I want to have several instances of the same component on the page?
The only solution I can come up with is to parameterize the component on instantiation, so that if I have a "person" component that uses the datepicker to set the day of birth of the person, the date-picker would be parameterized so that it would emit a global event such as [:set-person-day-of-birth <person-id> [2017 1 16]]. Is this how re-frame approaches hierarchies?
But if the component is larger (that is, not just the view, but also the handlers and subscriptions -- I'd probably call it something else, a service maybe...) then its a bit less simple because not just the view, but also the handlers and subscriptions need to be aware of the "instance" they are running. There's no well defined re-frame solution for this yet.
My favourite solution right now is to have each instance have its own ID as explained here: https://github.com/Day8/re-frame/issues/264#issuecomment-260...
That is, by convention, subscriptions are always [query-name instance-id <other optional data>] and messages are always [message-type instance-id <other optional data>] by convention. This way works quite well, but has the downside that it is by convention and there's no guarantee that other code will do it the same way.
FYI, I just tried to address those issues in a small Elm-ish wrapper on top of re-frame
My reasoning was for conpatibility and consistency. If everyone makes up their own argument structure, it becomes very difficult to maintain and puts the onus on the developers to remember which components use which structure. I'm not really asking for much beyond standardising that the first argument of an instanced event of subscription query is the instance id.
* waaay less boilerplate
* very neat concept of "reactive subscriptions network", which is a way to minimize re-renderings. Let's say you have keys "items", "sortOrder" and "mouseX" in your storage; how do you avoid rerendering your components each time user moves their pointer? Moreover, let's say you have a list of "items" that is sorted in "sortOrder". How do you avoid re-sorting? Re-frame allows you to skip updates entirely, neatly minimizing change propagation and avoiding React re-rendering in most cases.
* there is a sane story around complex async processing, namely [1]. It's similar in purpose to redux-saga, but simpler, more explicit, debuggable and compatible with time-travel and everything.