Skip to content

Instantly share code, notes, and snippets.

@jkrasnay
Last active June 17, 2022 08:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkrasnay/12ed298d1058d47ef6714618753a081f to your computer and use it in GitHub Desktop.
Save jkrasnay/12ed298d1058d47ef6714618753a081f to your computer and use it in GitHub Desktop.
Re-frame Subscription Helpers

Re-frame Subscription Helpers

There are a couple of common problems with Re-frame subscriptions that I’d like to solve:

  1. Because subscriptions are registered via keywords, the ClojureScript compiler does nothing to help check that we haven’t made a typo when subscribing.

  2. We often need to use the same logic to access a value in the app DB in both a subscription and in events.

I’ve come up with the following structure. First we create the accessor function, which can be used by both the subscription and any events. (This of course implies that the namespace where we implement our events must refer to the namespace where we define our subscriptions.)

(defn widgets-by-type
  [db t]
  (->> (:widgets db)
       (filter #(= t (:type %)))))

We then create the subscription based on the accessor.

(rf/reg-sub
  ::widgets-by-type
  (fn [db [_ t]]
    (widgets-by-type db t)))

Finally, we create a helper function for subscribing from within a component.

(defn <-widgets-by-type
  [t]
  @(rf/subscribe [::widgets-by-type t]))

Most components will use the last function to inject subscription values.

[:div (<-widgets-by-type :gadget)]

I’ve created a macro that allows us implement this pattern in one form.

(defsub widgets-by-type
  (fn [db t]
     (->> (:widgets db)
          (filter #(= t (:type %))))))

Here is the macro definition.

(defmacro defsub
  [sym & body]
  (let [kw (keyword (str *ns*) (name sym))
        impl (last body)
        [_ [db & args] & impl-body] impl
        ]
    `(do

       (defn ~sym
         [~db ~@args]
         ~@impl-body)

       (rf/reg-sub
         ~kw
         ~@(butlast body)
         (fn [~db [~(symbol "_") ~@args]]
           (~sym ~db ~@args)))

       (defn ~(symbol (str "<-" sym))
         [~@args]
         (deref (rf/subscribe [~kw ~@args]))))))

The macro supports chained subscriptions by just copying them over reg-sub call. This makes defining subscriptions with defsub almost identical to reg-sub, with the exception that (a) we use a symbol instead of a keyword, and (b) we don’t need to destructure our event vector.

(defsub complex
  (fn [[_ arg1] _]
    [(rf/subscribe [:foo arg1])
     (rf/subscribe [:bar arg1])])
  (fn [[foo bar] arg1]
    ,,,))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment