This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-10-02
Channels
- # 100-days-of-code (1)
- # announcements (2)
- # beginners (122)
- # boot (5)
- # calva (5)
- # cider (54)
- # cljdoc (1)
- # clojure (132)
- # clojure-brasil (1)
- # clojure-italy (4)
- # clojure-nl (6)
- # clojure-uk (105)
- # clojurescript (43)
- # core-async (17)
- # cursive (14)
- # datomic (60)
- # emacs (35)
- # figwheel-main (44)
- # fulcro (70)
- # graphql (1)
- # jobs (19)
- # jobs-discuss (5)
- # leiningen (5)
- # luminus (2)
- # off-topic (40)
- # onyx (2)
- # overtone (5)
- # re-frame (36)
- # reagent (29)
- # ring-swagger (20)
- # rum (13)
- # shadow-cljs (19)
- # testing (5)
- # tools-deps (25)
- # vim (5)
@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
@andrew354 Thanks, that helps
Has anyone tried using clojure.spec
on re-frame handlers?
Specifically fdef
and instrument
I've made an experimental fork of re-frame that eliminates the registrar namespace (code here: https://github.com/tomconnors/re-frame/commit/c2b179373306c19839e51beff4a6cf92510e68c0). 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 :require
ing 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.
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.
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
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.
@andrew354, do you use assert
on the result then?
(rf/reg-event-db
:example
(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 interceptorAh, 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
Ah, ok. I hadn't thought of redefining the reg functions. Thanks
(defn validate-db []
(->interceptor
: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?)
db
(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)))))
ctx)))
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.
Gotcha :thumbsup::skin-tone-2:
Thanks
Also experimented with macros that wrap the existing registration macros to remove some of the boilerplate, including defining a set of common interceptors.
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 fdef
s 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.
@andrew354, do you have that db validation interceptor in production?
How is it switched on and off?
Ah, ok
I thought maybe s/valid?
had some sort of flag I wasn't aware of.
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 for me on MacOS
might try clicking in the browser app window (outside the 10x window) first?