Skip to content

Dynamic Subscriptions

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

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 on the project, 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 (add link)

(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. However this is a bit dirty.

The second way is with dynamic subscriptions.

The hero enters

Dynamic subscriptions allow the user to create subscriptions that depend on Ratoms/Reactions and will be rerun when they change. Users will subscribe with v and a vector of dynamic values. The dynamic values are dereffed and passed to the handler-fn. Dynamic subscriptions need to pass a fn which takes app-db, v, 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 %))))))))