Skip to content

Dynamic Subscriptions

Daniel Compton edited this page Dec 16, 2015 · 17 revisions

Attention! This page is still under construction! What's written here could be misleading, out of date, incorrect, or just plain wrong.


Introduction

In re-frame, to create a subscription, you subscribe with a vector like [:todos]. You can also pass additional parameter to your subscription by adding them to the vector like this: (subscribe [:todos "home-list"]). However these subscription values are static. If you want your subscriptions to update with dynamic parameters taken from app-db then you are forced to resort to dirty hacks that make Dan Holmsand turn in his sleep. Dynamic Subscriptions are the answer to this problem.

An example

Let’s say we work at todoit.computer and we’ve just got $1M seed venture funding for our todo app. Our intial MVP with re-frame only had a single list, but the investors want to see multiple todo lists by the end of the quarter or they’re pulling the pin. You’re the lead developer, and the CEO is on your back about it. Let’s get started!

Our initial view looks like this, using a form-2 reagent component

(defn  todos-list
    [] 
    (let [list    (subscribe [:todos-list])]
        (fn []
        ... render the list)))

The first thing you might think of (we did too!) is something like this:

;; Don’t use this code!!!
(defn  todos-list
    [] 
    (let [list-id (subscribe [:list-id])
           list    (subscribe [:todos-list @list-id])]
        (fn []
        ... render the list)))

However this has a major flaw! list-id is dereferenced once when the view is initially rendered. When app-db changes, this code won’t be rerun, and you’ll be stuck with the first value that was dereferenced.

A dirty hack, is to do this:

(defn  parent 
    [] 
    ^{:key @list-id}[todos-list list-id])

and pass list-id to to the todos-list function. This works, because whenever list-id changes, then the entire component is blown away, because the key is different. This is a bit dirty, and if the CTO catches wind of it she won't be happy.

The second way is with dynamic subscriptions.

Our hero enters

Dynamic subscriptions allow you to create subscriptions that depend on Ratoms or Reactions (lets call them Signals). These subscriptions will be rerun when the Ratom or Reaction changes. You subscribe as usual with a vector like [:todos-list], and pass an additional vector of Signals. The Signals are dereferenced and passed to the handler-fn. Dynamic subscriptions need to pass a fn which takes app-db, the static vector, and the dereffed dynamic values.

Every time a dynamic value changes, handler-fn will be rerun. This is in contrast to standard subscriptions where handler-fn will only be run once, although the reaction that it produces will change over time.

(register-sub
  :todo-dynamic
  (fn todo-dynamic [_ _ [active-list]]
    (let [q (q/get-query active-list)]
      q)))

(register-sub
  :todos
  (fn todos [db _]
    (let [active-list (subscribe [:active-list])
          todos       (subscribe [:todo-dynamic] [active-list])]
      (make-reaction (fn todo-vals [] (update @todos :result #(vals (:list %))))))))

;; TODO: show view code here too