Re-frame provides a very sane model for producing even complex applications. The clear separation of responsibilities and the simple data-flow (esp. once effects & co-effects come into the picture) are a powerful combination. While I have wryly observed its hegemony, good taste forbears any significant use of Javascript. ClojureScript + Re-frame make browser based development fun.
Completely agree, explicit effects and coeffects made it sooo much clearer. The only downside I can think of is that the names make the idea seem more complex and can remind of Haskell, but in reality it's very simple and useful abstraction.
The last time I wrote any ClojureScript was over a year and a half ago. I played around with Reagent a bit and remember really liking it. I never used it for any serious projects or in anger though - how is the tooling and debugging experience? I would love to use ClojureScript for my front end work (since I always end up using React + ImmutableJS anyways) but developer ergonomics are really important to me.
Well, the CLJS->JS side of story is very polished, Figwheel [1] makes things work smoothly (in-browser errors and warnings, sourcemaps, hot reload all work from the box). You can check out [2] if you are interested.
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.
Fighwheel and cljs-devtools (https://github.com/binaryage/cljs-devtools) makes working with ClojureScript very ergonomic. The regularity that re-frame brings (knowing where all data resides and exactly what can change it) makes it even nicer.
As others have mentioned Figwheel and DevTools certainly improve the picture. There is also re-frisk which is an embedded, real-time, event monitor and database viewer. It's early days but already very helpful.
The debugging experience still requires thinking in javascript (https://drive.google.com/file/d/0Bz3IhEqTy9ioZWQ1T24tSzVwaW8...). It's not that big a deal from a day to day. Obviously still worth it, clojurescript is amazing and no other language comes even close. I dont use a ClojureScript repl these days either but I know some are and I need to revisit it.
I haven't gotten a chance to try it yet (would be interested in hearing from anyone who has), but there is a Chrome devtools fork called Dirac that is targeted at natively debugging clojurescript.
That's weird, but I believe I remember seeing some DHH writings talking about coding in anger around the time he was defending cursing and sexually suggestive mostly-nude slides at Ruby confs[1]. I blew the phrase off as as a DHH-ism, thinking maybe he believed in trying to remain angry while coding or something crazy like that. Thanks for explaining the term!
I don't know what DHH meant, but the idiom "in anger" is used to mean "in seriousness", "for real" or "properly". Whether or not DHH meant the idiom, I do not know. But its likely.
For example: "I've been tinkering with emacs, but I've yet to use it in anger."
I love this README, not least because it provides some essential missing documentation for Reagent (the different component "forms").
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)
I just read the readme, but couldn't understand what the differences are with react ( except for the language of course). To me they are both running a single render loop to update and render views from a global state, with some kind of dom diffing algorithm in the end.
It's not a criticism, and I'm not a react programmer btw, just curious.
React is a rendering library. That is, you create a tree of widgets (components in react lingo) and feed them data. React then manages the rendering (turning it into the DOM) for you whenever this data is changed. It does it in a generally efficient way.
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).
Thank you for your great explanation. I've read the documentation of re-frame, but I still can't figure out how it deals with reusable components and hierarchies.
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?
I would say you don't. I think about it this way: use re-frame to write applications, but components must be self contained and have no knowledge of reframe. To do what you asked, I'll pass a function to the component. The function can then make the context-aware reframe call. That way all your component needs to do is to call the passed in function.
It depends on what you mean by component. Reagent components (ie UI widgets), I agree. Components should be self-contained and as re-frame agnostic as possible. So instead of subscribing to the data they want, they could take in a ratom as an argument (because the subscribe function returns one anyway).
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.
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.
Of course yes. My reply was largely tied to the parent's description of a date-picker component. But for application level components I'd use event names that are unique at the application level that conveys the intended semantics of the event. Everything else becomes an argument to that name, which pretty much aligns with your notion of instance Id et al although I'd be less rigid about the particular structure of the arguments.
although I'd be less rigid about the particular structure of the arguments.
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.
It's an answer to the question of "how do I manage state in SPA?" It is similar to redux, but:
* 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.