Fork me on GitHub
#re-frame
<
2020-11-18
>
paulbutcher10:11:46

I feel like this should be an FAQ, but I’m damned if I can find anything anywhere. Is there such a thing as a “weak” subscription in re-frame? By which I mean the ability to have a component use some state within app-db without updating whenever it changes? Why do I want this? I am reusing a large pre-existing Javascript component (Vega). Occasionally (because reasons) I need to re-create that Javascript component, and when I do so I want to re-initialise it with some of the dynamic state stored within the previous instance. So I’m cacheing that dynamic state within app-db and want to use it when I re-create the Vega component, but not update the component when the cache changes. There are clearly ways that I can address this (e.g. store the cached state within a normal clojure.core/atom , or “reach around” re-frame and access app-db directly) but it strikes me that what I really want is a way to tell re-frame that I’m interested in the value, but don’t want to update when it changes?

p-himik13:11:13

A direct access to app-db won't work by itself because it's a ratom - changes to it will still trigger component re-rendering if you deref it within the component's render function. With that being said, you can try deref'ing the subscription outside of the render function. Something like:

(defn my-component []
  (let [not-updating-state @(subscribe [...])]
    (fn []
      [:div not-updating-state])))

p-himik13:11:38

I'm not 100% sure but I think it will work.

paulbutcher13:11:51

Good point about accessing app-db thanks. And I will give that a try 👍

paulbutcher13:11:53

(although I’m coming around to the idea that a “normal” clojure.core/atom might be the least unpleasant if this really isn’t something that re-agent supports out of the box).

p-himik13:11:44

Well if my approach works then it's something that Reagent does support out of the box. ;) But your initial intention might be ill-placed. If what you cache is genuinely a part of your app's state then all is good. But if it's just a cache that can be reset at any moment without affecting the app in any meaningful way then I would argue that it should not be stored in app-db.

paulbutcher13:11:56

It’s state that would be in app-db if I was implementing the Vega component myself. But it’s a “traditional” Javascript component which maintains its state internally so that option isn’t open to me. But I’m definitely doing something off the normal beaten track, I know!

paulbutcher13:11:26

(ultimately, I’m planning to implement something that allows collaborative editing, so I’ll need to broadcast this state to other people looking at the same diagram, and that will definitely be easier if I can use re-agent’s subscription mechanism to facilitate it)

tomc14:11:44

Would binding reagent's ratom-context to nil work in this situation?

(binding [reagent.ratom/*ratom-context* nil]
    @(rf/subscribe [:sub]))
What I gather from reagent's code is that this makes the deref non-reactive.

p-himik15:11:12

AFAIK *ratom-context* is not a part of the public API, so even if it does work you still shouldn't use it as long as there are other solutions.

tomc15:11:36

Good point :thumbsup:

knubie13:11:35

what’s everyone using for routing in their re-frame app?

manutter5113:11:17

I’m using reitit

👍 15
p-himik14:11:44

I'm using reitit and bidi. I would bet that for every more or less known CLJS router there's a user of it here.

galdre17:11:06

I noticed something weird in my re-frame app, where extractor functions didn’t seem to be providing any benefit — in fact they seemed to make computation exponentially worse. I’ve boiled the example down to a few lines of code. I ran into the issue in 1.1.0, but it still seems to be there in 1.1.2. I feel like I must be doing something very wrong, so any help would be appreciated! Basically (code to follow), the library’s “propagation pruning” does not appear to be working for me. Dereferencing a subscription that uses another subscription as input causes that underlying subscription to be dereferenced twice. Another layer in the signal graph, and the base layer gets recomputed four times. Etc.

galdre17:11:11

(require '[re-frame.core :as rf])

(rf/reg-event-db ::assoc
  (fn [db [_ k v]] (println ["assoc" k v]) (assoc db k v)))
(rf/reg-sub ::get
  (fn [db [_ k]] (println "get " k) (get db k)))


;; Initialize some data:
(rf/dispatch [::assoc :a 'a])

(= 'a @(rf/subscribe [::get :a])) ; "get :a" prints as a side effect


;; Let's build a new subscription in the signal graph:
(rf/reg-sub ::a-derived
  :<- [::get :a]
  (fn [v _] (println "derived from a") (keyword v)))

(= :a @(rf/subscribe [::a-derived])) ; "get :a" prints TWICE as a side effect.


(= :a @(rf/subscribe [::a-derived])) ; no data has changed, but "get :a" still prints TWICE.


;; That 2x, how bad is it?
(rf/reg-sub ::double-derived :<- [::a-derived] (fn [kw _] (println "double derived!") (str kw)))


(= ":a" @(rf/subscribe [::double-derived])) ; "get :a" prints four times, "derived from a" prints twice

;; Maybe I should stop re-subscribing each time?
(def double-derived (rf/subscribe [::double-derived]))

@double-derived ; "get :a" prints four times, "derived from a" prints twice.
@double-derived ; "get :a" prints four times, "derived from a" prints twice.

(rf/reg-sub ::triple-derived
  :<- [::double-derived]
  (fn [s _] (println "triple derived!!") (str "Behold: " s)))

(def triple-derived (rf/subscribe [::triple-derived]))
@triple-derived ; "get :a" prints eight times
@triple-derived ; "get :a" prints eight times

galdre17:11:20

In my app, of course the subscriptions are dereferenced inside reagent views — this is just a REPL-friendly minimal example. Again, if anyone can tell me where I’m going wrong, I’d greatly appreciate it!

galdre17:11:45

Interesting — on further testing, I think that this was a phantom bug caused by my initial attempt to verify propagation pruning. With more careful testing in the app, propagation pruning does seem to work. So I guess my question is, why does it work the way it does in that minimal example? And what’s the difference between a deref inside a leaf node (view) and a deref at the REPL?

p-himik17:11:59

Dereferencing subscriptions is designed to work properly only within render functions and reactions. I don't know the exact reasons for why you see that behavior, but inside the render functions reagent.ratom/*ratom-context* is properly set. I'm just treating deref'ing subs anywhere other than render functions as UB.

galdre17:11:55

@U2FRKM4TW Thank you, that is very helpful. The bug was a case of the observer effect, which is always confusing.

galdre17:11:53

And thank you for the fast response!

p-himik18:11:00

Actually, it's a pretty nice find - I debugged it a bit and created https://github.com/day8/re-frame/issues/657.

galdre18:11:22

Ah, I hadn’t yet considered the effect for injection.

galdre18:11:23

Thank you (again)!