Fork me on GitHub
Oliver George01:07:52

Here's some notes on how we're using :fx to compose behaviours within handlers.

Oliver George01:07:16

Not sure if that will be interesting to people. Happy to answer any specific questions.

Aaron Decker17:07:56

Question about the subscription lifecycle: When a subscription is provided by a reaction created with reagent.ratom/make-reaction, does it have to be first dereferenced in order for the :on-dispose function to trigger once the component that first grabbed a reference to that subscription goes out of scope? Context: I'm creating a subscription that returns a value from a webservice (in this case, a Stripe checkout session ID). The parameters to the webservice varies by plan and a redirect-uri. The session IDs expire every 24 hours, so I'm using js/setTimeout to keep them refreshed but would like to clean up the timeouts using :on-dispose. When testing, it appeared that :on-dispose only gets called after the subscription reference has first been dereferenced.

(defn stripe-checkout-session-reaction [plan redirect-uri]
  (let [reaction (r/atom nil)
        refresh-timeout-id-atm (r/atom nil)]
    (fetch-stripe-checkout-session-into-atom plan redirect-uri reaction (* 23 60 60 1000) refresh-timeout-id-atm) ;; this calls a web service and puts the return value into reaction. It also runs a js/setTimeout in order to refresh the value every 23 hours.
     (fn []
       (println "In @reaction: " plan redirect-uri reaction)
     :on-dispose (fn []
                   (println "In on-dispose: " plan redirect-uri)
                   (when-let [timeout-id @refresh-timeout-id-atm]
                     (js/clearTimeout timeout-id))))))

 (fn [_ [_ plan redirect-uri]]
   (println "reg-sub-raw: " plan redirect-uri)
   (stripe-checkout-session-reaction plan redirect-uri)))

;; elsewhere in code, reagent components create a reference to the sub in an r/with-let
pro-checkout-session-id-atm (rf/subscribe [:stripe-checkout-session! :pro (.-location js/window)])
;; and then pass around the reference until the place where it is dereferenced.


IIRC, your hypothesis is correct. If you call subscribe somewhere, you must deref it in the render function. Not in a re-frame/JS event handler either - just the render function itself.


To clarify the last part - do not do this:

[:button {:on-click #(do-something @(subscribe [...]))}

Aaron Decker17:07:01

That's good to know. I presume you'd recommend against something like:

(defn my-button []
   (r/with-let [my-sub (subscribe [...])]
       [:button {:on-click #(println @my-sub)}
for the same reason?


Yep! Instead, do this:

(defn my-button []
  (let [v @(subscribe [...])]
    [:button {:on-click #(println v)}


You can call subscribe multiple times as well - the reactions are cached, it won't incur too much overhead.

Aaron Decker17:07:28

Thanks 🙂 My particular problem in this case is that the web call takes ~8 seconds, so I want to create the subscription on a UI element near the start of the payment workflow and then deref the value on a totally different element (though the first element is still mounted in the DOM). It seems in this case that re-frame subs aren't the best fit (though just passing around the subscription reference has worked surprisingly well).

Aaron Decker17:07:30

... though your point about the reactions being cached might actually make this work out. I'll give that a try.


> I want to create the subscription on a UI element near the start of the payment workflow and then deref the value on a totally different element Why? I don't see how it follows from some web call taking 8 seconds.


you should probably split the fetch from the subscription

Aaron Decker18:07:50

There's an upgrade button that opens a dialog that lets the user choose which price tier they want. The purpose was to fire off the web call when the first upgrade button was mounted, so that by the time the user opened the dialog it would already have returned, so that they wouldn't have to wait for the button that actually needs the session ID to load.

Aaron Decker18:07:46

The reason I didn't want to pre-fetch everything is that the Stripe checkout session depends on which URI you want to redirect to, which in this case changes based upon where the user is in the app when they initiate the upgrade/payment workflow.

Aaron Decker18:07:35

But yeah, I might end up just having the first element dispatch an event to do the fetch and then store that off in app-db where the subscription can get it.


Yeah, with re-frame, never link anything to a render. Just dispatch re-frame events from JS event handlers like :on-click, from effects, from other re-frame event handlers (via :dispatch or :fx - not by calling dispatch explicitly). Ideally, you should have only one dispatch call that's outside of all that - something in your "main" function that's called when the app is loaded. And if you need to listen for a change of some value in app-db, there are interceptors for that, with an ability to register them globally. It can replace doing something on mount when that mount is a consequence of some value change, like [:panels :user-info :visible?] becoming true.

👍 2

Hello. Can re-frame-10x be used with a react native app that's using re-frame? If not, what method(s) can be used to see what events are running, aside from log statements?


Oh, it looks like re-frisk can be used for this. Any other suggestions would be appreciated.