Liking cljdoc? Tell your friends :D

Subscriptions Cleanup

There's a problem and we need to fix it.

The Problem

The simple example, used in the earlier code walk through, is not idomatic re-frame. It has a flaw.

It does not obey the re-frame rule: keep views as dumb as possible.

A view shouldn't do any computation on input data. Its job is just to compute hiccup. The subscriptions it uses should deliver the data already in the right structure, ready for use in hiccup generation.

Just Look

Here be the horrors:

(defn clock
  []
  [:div.example-clock
   {:style {:color @(rf/subscribe [:time-color])}}
   (-> @(rf/subscribe [:time])
       .toTimeString
       (clojure.string/split " ")
       first)])

That view obtains data from a [:time] subscription and then it massages that data into the form it needs for use in the hiccup. We don't like that.

The Solution

Instead, we want to use a new [:time-str] subscription which will deliver the data all ready to go, so the view is 100% concerned with hiccup generation only. Like this:

(defn clock
  []
  [:div.example-clock
   {:style {:color @(rf/subscribe [:time-color])}}
   @(rf/subscribe [:time-str])])

Which, in turn, means we must write this time-str subscription handler:

(reg-sub 
  :time-str 
  (fn [_ _]  
    (subscribe [:time]))
  (fn [t _] 
    (-> t
       .toTimeString
       (clojure.string/split " ")
       first)))

Much better.

You'll notice this new subscription handler belongs to the "Level 3" layer of the reactive flow. See the Infographic.

Another Technique

Above, I suggested this:

(defn clock
  []
  [:div.example-clock
   {:style {:color @(rf/subscribe [:time-color])}}
   @(rf/subscribe [:time-str])])

But that may offend your aesthetics. Too much noise with those @?

To clean this up, we can define a new listen function:

(defn listen 
  [query-v]
  @(rf/subscribe query-v))

And then rewrite:

(defn clock
  []
  [:div.example-clock
   {:style {:color (listen [:time-color])}}
   (listen [:time-str])])

So, at the cost of writing your own function, listen, the code is now less noisy AND there's less chance of us forgetting an @ (which can lead to odd problems).

Say It Again

So, if, in code review, you saw this view function:

(defn show-items
  []
  (let [sorted-items (sort @(subscribe [:items]))]  
    (into [:div] (for [i sorted-items] [item-view i]))))

What would you (supportively) object to?

That sort, right? Computation in the view. Instead, we want the right data delivered to the view - its job is to simply make hiccup.

The solution is to create a subscription that delivers items already sorted.

(reg-sub 
   :sorted-items 
   (fn [_ _]  (subscribe [:items]))
   (fn [items _]
      (sort items))

Now, in this case the computation is a bit trivial, but the moment it is a little tricky, you'll want to test it. So separating it out from the view will make that easier.

To make it testable, you may structure like this:

(defn item-sorter
  [items _]
  (sort items))
  
(reg-sub 
   :sorted-items 
   (fn [_ _]  (subscribe [:items]))
   item-sorter)

Now it is easy to test item-sorter independently.

And There's Another Benefit

re-frame de-duplicates signal graph nodes.

If, for example, two views wanted to (subscribe [:sorted-items]) only the one node (in the signal graph) would be created. Only one node would be doing that potentially expensive sorting operation (when items changed) and values from it would be flowing through to both views.

That sort of efficiency can't happen if this views themselves are doing the sort.

de-duplication

As I described above, two, or more, concurrent subscriptions for the same query will source reactive updates from the one executing handler - from the one node in the signal graph.

How do we know if two subscriptions are "the same"? Answer: two subscriptions are the same if their query vectors test = to each other.

So, these two subscriptions are not "the same": [:some-event 42] [:some-event "blah"]. Even though they involve the same event id, :some-event, the query vectors do not test =.

This feature shakes out nicely because re-frame has a data oriented design.


Previous: Infographic       Up: Index       Next: Flow Mechanics       

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close