Search code examples
clojurescriptdereferencereagentre-frame

ClojureScript Re-frame subscription dereferencing dilemma


What's the best of following approaches?

Outer subscription, early deref

(defn component [msg]
    [:p msg]))

(let [msg (rf/subscribe [:msg])]
    [component @msg]

Outer subscription, late deref

(defn component [msg]
    [:p @msg]))

(let [msg (rf/subscribe [:msg])]
    [component msg]

Inner subscription, early deref

(defn component []
   (let [msg @(rf/subscribe [:msg])]
      [:p msg])))

Inner subscription, late deref

(defn component []
   (let [msg (rf/subscribe [:msg])]
      [:p @msg])))

When I keep the inner component pure using outer subscription, I can end up with many arguments that need to be passed through deeply nested structure of often unrelated parents. That can easily become a mess.

When I subscribe inside inner component, it becomes impure, losing easy testability.

Also, I wonder if there is an important difference between early and late dereferencing, other than I have to pass reagent/atom when testing the latter.


Solution

  • We'll the answer, as always, is "it depends", but ...

    Outer subscription, early deref leads to pure/testable inners. So that could be a good choice when that's important to you. But we don't use this style much.

    Outer subscription, late deref we've actively moved away from this style because it produced code which we later found hard-to-understand. BTW, if ever we do pass ratoms/cursors/subscriptions around, we like to put a trailing * on the argument name to make it clear they are a reference-y thing, and not a value.

    Inner subscription, early deref is probably the most used, I'd guess. Feels very natural after a while. Perhaps use <sub from LIN

    Inner subscription, late deref this works too, but I tend to prefer the variation directly above. There's always a nagging worry that you might forget to add @ at the point of use, and that can be an annoying bug to find.