re-frame

https://github.com/Day8/re-frame/blob/master/docs/README.md https://github.com/Day8/re-frame/blob/master/docs/External-Resources.md
jmckitrick 2020-10-18T14:38:16.147800Z

After some time away, I’m back to re-frame and loving it. I’m beginning a non-trivial (not a toy) project, and I’m trying to put some serious thought into naming conventions. On the view side, I have components (form, table, clickable button, etc), containers (subscriptions to data, passed down to child components), views (groups of containers), and pages (with a few views). The app is mostly tables, forms and eventually PDF reports (non-re-frame of course). Is that too many moving parts?

jmckitrick 2020-10-18T14:39:06.148800Z

I want a naming scheme that isn’t all over the place, where I can tell at a glance if a keyword is an effect, a subscription, a view, etc. while at the same time avoiding duplication and overly verbose names.

jmckitrick 2020-10-18T14:39:34.149500Z

So a successful http call would dispatch :account/data and :user/data rather than :event/account-data and :event/user-data, for example.

jmckitrick 2020-10-18T14:39:49.149900Z

Any other suggestions, best practices, blog pages on the topic, etc?

Kamal Sadek 2020-10-19T15:14:03.162800Z

@p-himik The way we do it with our product, we only have views.cljs and control.cljs, where the control is responsible for all events, data, etc. and turning it into an easier representation that can be called out to by the views. While it does sometimes run into the issues that you describe (i.e. if we have extremely simple events / getters and setters), I think it's a bit less cumbersome than the 4 different namespaces described above, and the structure does help break things up, and keep things a bit modular. Just mentioning this because I'm also a bit curious as to how you organize things personally / how you recommend doing this. Do you simply have one ns for each Component / Page in the application? I've primarily used similar patterns that break things up like this (MVC, etc.), and it usually does help keep code cleaner and a bit more modular, but wondering if it's not as much of an issue in a Clojure based stack.

p-himik 2020-10-19T15:15:47.163100Z

> Do you simply have one ns for each Component / Page in the application? Yes, with some justified ad-hoc exceptions.

👍 1
Kamal Sadek 2020-10-19T15:20:11.163300Z

Interesting, I'll keep that structure in mind then!

jmckitrick 2020-10-19T16:14:24.163600Z

What I’m aiming for is smart re-use. So a clickable button, for example, can be styled, etc but you pass in the event you want sent with the click handler, plus any relevant args grabbed from immediate component context. A table is more complex, but could be similar with a vector of column headers a vector of row maps. Anything special can be an event passed in and sent off with the dispatch call. Then a container could fire off events, subscribe to results, and pass those atoms down into components. Pretty basic stuff, really.

jmckitrick 2020-10-19T16:15:16.163800Z

But I want to make sure the namespaced keywords are an asset rather than an annoyance when it’s time to refactor.

p-himik 2020-10-19T16:19:42.164Z

I wouldn't put refactoring costs too high on the priority ladder. Otherwise, abominations AbstractSingletonProxyFactoryBean would appear. :) Both kinds of keywords are just keywords. You refactor them like anything else, especially if your development environment treats them as first-class citizens. The only way namespaced keywords can become cumbersome is if you move a keyword like ::x into a different namespace. Maybe your DE supports that just fine, and then there are no problems whatsoever. Maybe mine IDE supports that as well, but on average I move about one keyword every 2-3 months I'd say, so I never bothered to even check.

jmckitrick 2020-10-20T18:59:19.177Z

So with a separate ns for each component and page, where do you get your reuse, particularly of components?

Kamal Sadek 2020-10-20T19:08:29.177200Z

Not sure if this is exactly what you're going for @jmckitrick, but here's a snippet of what it would look like for us:

(ns example.callback-button
  (:require [reagent.core :as r]))

(defn CallbackButton
  []
  (let [num-times-clicked (r/atom 0)]
    (fn [{::keys [callback text]}]
      [:button {:on-click #(do (swap! num-times-clicked inc)
                               (callback))}
       (str text " " @num-times-clicked)])))
(ns example.main-page
  (:require [example.callback-button :as cb]))

(defn Component
  []
  [cb/CallbackButton #::cb{:text     "A button"
                           :callback #(print "A callback")}])
(defn AnotherComponent
  []
  [cb/CallbackButton #::cb{:text     "Another button"
                           :callback #(print "Another callback")}])

Kamal Sadek 2020-10-20T19:11:10.177400Z

With most of the complexity of the component in the respective ns, and other pages / components simply pass the high level info needed

p-himik 2020-10-20T22:06:42.180900Z

> So with a separate ns for each component and page, where do you get your reuse, particularly of components? Not sure I understand the question. You have e.g. a my-proj.calendar ns, and you use it just as is, as a whole. If you need to share e.g. a single event handler function out of that ns, then you have to think what kind of dependency that is. Is it something that belongs to the calendar but is just used some place else, or is it something just common for different things? If the former, you don't extract that function and just keep using my-proj.calendar. If the latter, you extract it into my-proj.common or whatnot.

jmckitrick 2020-10-21T02:08:38.182300Z

I’ll have to do some experimenting and see if I can come up with concrete cases.

jmckitrick 2020-10-21T02:09:51.182500Z

Maybe your approach of keeping all events and subs with their components eliminates the issue entirely.

jmckitrick 2020-10-21T03:02:52.182700Z

@p-himik So just to be clear, you’ll have a ns with views/components, and all events and subs are in that namespace, unless factored out for common use elsewhere. So that means namespaced keywords are resolved right there, in those namespaces.

p-himik 2020-10-21T06:40:57.183400Z

Indeed.

p-himik 2020-10-21T06:42:02.183600Z

If an event is used somewhere else, I just

(require '[my-proj.calendar :as calendar])

(dispatch [::calendar/set-date "2020-10-21"])
or something like that.

jmckitrick 2020-10-21T20:51:24.210Z

And you generally group the view, event, and subscription all in the same namespace and file?

jmckitrick 2020-10-21T20:52:09.210200Z

What about cases where, say, an http fx is used in different places? I guess a ‘common’ namespace or is there a better way? @p-himik

p-himik 2020-10-21T20:55:09.210500Z

Yep. And yes, just a common ns. Sometimes people extract fxs in a separate special fx ns, I tend to do the same for well-focused tiny effects and I create specialized namespaces for effects that serve the same purpose.

p-himik 2020-10-21T20:56:38.210700Z

IMO it's comparable to just writing functions in Clojure, which is (again, IMO) not that different from writing code in any modern and actively used language. Well, Clojure does have some polymorphism which may create some differences, but re-frame related stuff is not polymorphic so it's more similar to other languages.

p-himik 2020-10-21T21:02:50.210900Z

I have quite a lot of legacy code that does split functionality into views/subs/events, and I'm gradually refactoring it into single namespaces. Working with them, in my experience, proves to be much easier. And simpler, I think. :)

jmckitrick 2020-10-21T21:45:34.211100Z

Ok, that’s the route I’ll try… for some reason I like the idea of separate folders (views, pages, components, etc) because it seems better organized for navigation and reuse, but things are not always as they seem….

p-himik 2020-10-18T14:56:37.150600Z

I just use FQN with the current namespace, i.e. every event or subscription keyword ID always starts with ::.

jmckitrick 2020-10-18T14:59:36.150800Z

I know somewhere I read a reasonable argument against that approach, but I forget what it was. I could still go either way this early in the project.

jmckitrick 2020-10-18T14:59:43.151Z

Have you run into any drawbacks?

p-himik 2020-10-18T15:01:37.151200Z

Just a very minor one - it makes it slightly harder to move event handlers and subs around. But I don't think it really matters.

jmckitrick 2020-10-18T15:07:49.151400Z

Now that I think about it, it might have been related to spec more than re-frame…. I’m not sure.

jmckitrick 2020-10-18T15:07:57.151600Z

But your comment makes sense. I’ll give that a shot.

valtteri 2020-10-18T15:34:18.151800Z

I’ve had good experience with putting each “sub-page” under separate folder that has • routes.cljsview.cljsevents.cljssubs.cljs

valtteri 2020-10-18T15:34:55.152Z

Each sub-page is also a namespace

jmckitrick 2020-10-18T15:40:00.152300Z

Oh, yes, I recall. double-colon namespace keywords are harder to search for in your codebase. I think that was it…..

valtteri 2020-10-18T15:44:10.152500Z

You can always write them without the :: shortcut. 🙂

p-himik 2020-10-18T15:55:43.152700Z

Regarding separating functionality into views/events/subs files - I don't do that. Frankly, I think it's counterproductive. E.g. suppose you have a simple sub that's implemented using just get-in and that's used by a single view. And the event that sets that value is implemented using just assoc-in. Now why would you ever want to separate these things? Usage is in one file, setter is in another, getter is in yet another one - it makes it much harder to navigate and get a glimpse at how something is working. @jmckitrick Regarding "harder to search" - do you use for Clojure development? Cursive can absolutely search for such keywords. I don't know for certain but I'm about 90% sure that VSCode, Vim, and Emacs plugins fur Clojure also support searching for such keywords.

jmckitrick 2020-10-18T16:08:17.152900Z

@p-himik CIDER might have caught up as well

willier 2020-10-18T21:40:37.154900Z

how do ppl handle testing of http xhrio effects? i want to write a test with mocked ajax responses.

p-himik 2020-10-19T07:25:02.159100Z

I would just replace the URL with something that guarantees the required HTTP code.