Fork me on GitHub
#re-frame
<
2023-01-10
>
DrLjótsson07:01:12

I often find that I want to get the value of a subscription inside a re-frame event. The primary reason is that I don't want to specify track the location of specific data in my app-db. Say that I have some data located in [:foo :bar :baz], I have a subscription :foo-bar-baz that returns this data. I have an event that also needs this data. I could separate out the retrieval function from the subscription (i.e., (rf/reg-sub :foo-bar-baz (fn [db _] (get-foo-bar-baz db)) and use in (get-foo-bar-baz db) in the event as well. However, that creates some boilerplate in my subscriptions. I have found that I can evaluate a subscription in the re-frame registrar, like this

DrLjótsson07:01:24

(defn eval-sub
  [db [query-id & args]]
  (if-let [f (re-frame.registrar/get-handler :sub query-id)]
    @(f db (into [query-id] args))
    (throw (js/Error. (str "Subscription not found " query-id)))))
I can call this like (eval-sub db [:foo-bar-baz]) and it will return the value of the subscription. I've checked the code for rf/reg-sub and it seems that eval-sub will create a reagent reaction but I believe that it is not retained so there will be no memory leak? But of course some overhead from creating the reaction. Is this crazy - is there a better way?

p-himik08:01:13

I'd say what you've described in the previous message, the approach with some boilerplate, is the way to do it, unless the computation isn't cheap, in which case you could cache it somewhere, e.g. in app-db, and maybe even use a global interceptor to recompute the value when needed. Your solution with a registrar is brittle - it relies on re-frame's internals that could potentially change.

🙏 2
DrLjótsson09:01:43

I get it. It would be nice though if there was a way to evaluate a subscription against the dbthat is received by an event without the boilerplate.

p-himik09:01:24

For simple subs that are without any input signals, you can write a macro that does it. :) Relatively trivial.

DrLjótsson10:01:53

That sounds interesting - although I don't fully get it. Could you expand a bit? 🙂

p-himik11:01:11

Something like

(defmacro my-reg-sub [name-sym args & body]
  `(do
     (defn ~name-sym ~args ~@body)
     (rf/reg-sub ~name-sym ~name-sym)))
Of course, this will reg the sub by a function object and not a keyword, so you'd have to use that object when you use the sub:
(ns proj.core
  (:require [proj.subs :as ps]
            [re-frame.core :as rf]))

(defn view []
  [:span @(rf/subscribe [ps/some-stuff])])
Also, note that the args must be a vector of 2 elements - [db args], in whatever form (can include destructuring, just like with a regular function). And so an event using that function must also supply the second value, even if it's always nil. But it's trivial to rewrite the macro to do that for you.

🙌 2
DrLjótsson14:01:54

Cool, thanks!

👍 2
DrLjótsson14:01:11

@U0J30HBRS, yes. But for different reasons it’s not feasible to use the injection pattern described there. The event handler does not know what what subscriptions are needed, it’s complicated 😊

👍 2