Fork me on GitHub

@dfcarpenter You can provide an argument to subscriptions, so in the view you might do something like @(rf/subscribe [::subs/list-item detail-id]) Then the list-item subscription can subscribe to another subscription that returns the whole list, and returns the specific piece you care about

Garrett Hopper16:10:43

Has anyone tried using clojure.spec on re-frame handlers?

Garrett Hopper16:10:05

Specifically fdef and instrument


I've made an experimental fork of re-frame that eliminates the registrar namespace (code here: Instead of using a mapping from keyword to handler, handlers are used directly, like (re-frame/dispatch [some-namespace/handle-event]). I'm hoping to get some feedback on whether this seems like a good idea, in case I've missed anything. The original motivation for getting rid of the registrar was code size: registered events, subs, etc, are included in the output js regardless of whether they are used. The registrar-less version gives modest size savings. After making a re-frame template app with lein new re-frame test-no-registrar, the compiled script size was 342K (90K gzipped). Without the registrar, 332K (87K gzipped). 3K is basically nothing, but the removal of the registrar has some other benefits: - libraries can no longer bloat compiled scripts with registered but unused handlers - no more implicit namespace dependencies. With a registrar, I can reference :my-great-subscription wherever, without :requireing its namespace in each namespace that uses it. Without a registrar, each namespace must declare its dependency on the namespace my-great-subscription lives in. - more compile-time safety: directly referencing vars means the compiler can tell us about typos in event/sub names. Some problems with this approach are: - Some re-frame patterns are no longer enforced by code. There's no longer any concept of "registering an effect handler" - you just use the effect handler where you need it. - It's a big breaking change, unlikely to be merged into re-frame - Others, I'm sure... The advantages aren't overwhelming, but I do think they outweigh the disadvantages enough for me to maintain my own fork.

Garrett Hopper16:10:42

I like the idea, though there have been a number of scenarios where I've have several interlinked namespaces and would get a circular dependency error if it weren't for the keyword registrar.

Garrett Hopper16:10:21

That may have just been bad design on my part though. I can't think of a good example at the moment.


Yeah, I've had that too, unrelated to re-frame though. Users could still define their own registry and use that to avoid circular deps

Garrett Hopper16:10:26

Fair point :thumbsup::skin-tone-2:


@ghopper I'm using spec to enforce that after every event handler runs that the app-db conforms, but not yet fdef on handlers themselves.

Garrett Hopper16:10:59

@andrew354, do you use assert on the result then?

Garrett Hopper16:10:51

 (fn [db]
   (s/assert :database/spec db)))


(defn check-and-throw
  "Throws an exception if `db` doesn't match the Spec `a-spec`."
  [a-spec db]
  (when-not (s/valid? a-spec db)
    (throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))

(def check-spec (rf/after (partial check-and-throw ::db)))
are the relevant bits, and then check-spec is added as an interceptor

Garrett Hopper16:10:10

Ah, nice. You have to manually add that interceptor to each handler, right?


definitely don’t do that. instead write an app-espcific reg-event-db / reg-event-fx which adds an interceptor

Garrett Hopper16:10:50

Ah, ok. I hadn't thought of redefining the reg functions. Thanks


(defn validate-db []
    :id :validate-db
    :after (fn [ctx]
             (let [db (get-in ctx [:effects :db])
                   untrimmed-event (get-in ctx [:coeffects :in/untrimmed-event])
                   event (get-in ctx [:coeffects :event])]
               (when (and (s/check-asserts?)
                          (not (nil? @spec))
                          (not (s/valid? @spec db)))
                 (log db)
                 (error (str "DB does not pass spec validation after
                                        event: \n" (or untrimmed-event event) "\n\n"
                                        ;(subs (s/explain @spec db) 0 1000)
                                        (s/explain-str @spec db)))))



for not that much code you can write an interceptor that handles it all for you. i’ve used this on a bunch of projects.


Yeah, although I have another def to name common-interceptors I think.

Garrett Hopper16:10:44

Gotcha :thumbsup::skin-tone-2:


Also experimented with macros that wrap the existing registration macros to remove some of the boilerplate, including defining a set of common interceptors.

Garrett Hopper17:10:21

I can't decide if I actually like this.

(s/def :database.props/example nil?)
(s/def :database/database
  (s/keys :req-un [:database.props/example]))
(s/fdef initialize
  :args (s/cat)
  :ret :database/database)
(defn initialize []
  {:example nil})
(rf/reg-event-db :initialize initialize)


I think fdefs would mostly be useful to specify some invariant property of the function transformation, and just let the whole app-db verify itself after handler execution.

Garrett Hopper18:10:19

@andrew354, do you have that db validation interceptor in production?


No, just dev.

Garrett Hopper18:10:51

How is it switched on and off?


a when clause on goog.DEBUG, which is set to true in closure-defines

👍 4
Garrett Hopper18:10:34

I thought maybe s/valid? had some sort of flag I wasn't aware of.


How is re-frame-10x meant to be closed on Mac OS?


Every combination of ctrl or cmd + h doesn't seem to do it (and cmd + h is a system binding for hiding the whole window).


ctrl + h works fine on GNU/Linux.


ctrl-h works for me on MacOS


might try clicking in the browser app window (outside the 10x window) first?


I had some issues with Chrome extensions, so might worth unable them, also check if ctrl-h is not overwritten as a shortcut in browser settings, I had that issue as well.