Fork me on GitHub
#re-frame
<
2021-04-21
>
Stuart Campbell06:04:26

I'm using re-frame-10x in a project that has a main module and a web worker module. The relevant part of shadow.cljs.edn looks something like:

:builds {:main {:target :browser
                :devtools {:preloads [day8.re-frame-10x.preload]}
                :modules {:common {:entries []}
                          :main {:init-fn foo.core/init
                                 :depends-on #{:common}}
                          :worker {:init-fn foo.worker/init
                                   :depends-on #{:common}
                                   :web-worker true}}}}
I get this error when the worker initializes:
Uncaught ReferenceError: document is not defined
    at subs.cljs:729
    at worker.js:1542
the offending line in subs.cljs being this one:
(def canvas (js/document.createElement "canvas"))
So my assumption is that re-frame-10x tries to initialize a canvas in the worker and errors out. My question is, how should I configure this so that re-frame-10x only loads in the :main module?

thheller06:04:04

move the :preloads into the :main module so they are not loaded by the worker.

thheller06:04:27

just :modules -> :main -> :preloads

Stuart Campbell02:04:39

lol, I don't know why I didn't just try that. Thank you 🙂

kennytilton06:04:04

Nooby Q on re-frame: when a reg-event-fx kicks off multiple dispatches do they each go thru a full six-domino event process? And if there is a :db effect, will that first propagate to all subscriptions before the first event gets dispatched? Most useful would be a pointer to where I can find documented the nitty gritty details of the r/f loop. I am looking at a view manifesting some churning and I need to get a little deeper than the high-level view. Thx!

kennytilton06:04:59

I thought I would give re-frame-10x a try as a learning tool and bumped it from 0.6.0 to 1.0.2. Now seeing ` ------ WARNING #1 - :fn-arity -------------------------------------------------- Resource: day8/reagent/impl/component.cljs:46:21 -------------------------------------------------------------------------------- 43 | {}) 44 | :operation (operation-name c)}) 45 | (if util/non-reactive 46 | (component/do-render c compiler) ---------------------------^---------------------------------------------------- Wrong number of args (2) passed to reagent.impl.component/do-render -------------------------------------------------------------------------------- 47 | (let [^clj rat (gobj/get c "cljsRatom") 48 | _ (batch/mark-rendered c) 49 | res (if (nil? rat) 50 | (ratom/run-in-reaction #(component/do-render c compiler) c "cljsRatom" when shadow-cljs starts up. Problem? Thx! 🙏

superstructor06:04:56

What version of reagent are you using ? @U0PUGPSFR

superstructor06:04:41

10x is pretty tightly bound to the internal/private APIs of reagent atm. So, 10x 1.0.2 only works with reagent 1.0.x for example.

kennytilton06:04:12

Thx! Checking....reagent/1.0.0. I just added a re-frame/1.2.0 dependency as well. Warning is gone. Thx again!

clyfe07:04:12

I believe I saw some re-frame docs a while back on doing fork-join of sorts. That is: dispatch two events and after both are handled, do a third. It can be done by each handler checking state to see if the other finished first. But I can't find that docs bit anymore. Does anyone know it?

mikethompson08:04:23

re-frame-async-flow @claudius.nicolae

❤️ 5
emccue14:04:18

@claudius.nicolae Unless it is truly a one time bootup sequence thing, I would avoid async flow for it

emccue14:04:40

lets say they click a button, firing event A, which sends off two http requests

emccue14:04:01

if they come back successfully they will fire B and C respectively

emccue14:04:10

and once you have all the data you can do the logic in D

emccue14:04:54

async flow only runs once, so if they click the button again unless you hack it (we've done it) you don't get the same control flow again

emccue14:04:43

and I say http requests partially to get you thinkin about what happens if one fails

emccue14:04:56

your "fork join" logic can't be retryed

emccue14:04:32

I would either flatten your events or start maintaining the state of what is going on explicitly

emccue14:04:04

(defn request-dataset-a [db]
  {:db (update db ::model/page-state
               assoc :dataset-a {:status :loading})
   :fx (fx/in-order ;; concat
         (http-fx/request ... 
                          :on-success #(rf/dispatch (success-dataset-a %))
                          :on-failure #(rf/dispatch (failure-dataset-a %))})

(defn request-dataset-b [db]
   ... same as above, replace a with b ...)

(defn both-done? [page-state]
  (and (= :success (:status (:dataset-a page-state))))
       (= :success (:status (:dataset-b page-state)))))

(defn when-both-done [{:keys [db fx]}]
  {:db db
   :fx (fx/in-order
          (alert-fx/alert "DONE!")
          fx)})
  
(defn handler:user-clicked-button 
  [{:keys [db]} _]
  (let [process (compose-effect-fns request-dataset-a
                                    request-dataset-b)
    db))

(defn handler:success-dataset-a
  [{:keys [db]} [_ data-a]
  (let [page-state (::model/page-state db)
        with-data  (assoc page-state :dataset-a {:status :success
                                                 :data   data-a})
        res        {:db (assoc db ::model/page-state with-data)
                    :fx []}]
    (if (both-done? with-data)
      (when-both-done res)
      res)))           

emccue14:04:12

if that makes sense

emccue14:04:43

the stuff that isn't handler: is kinda sloppy, i'll admit, but the general idea holds

Roman Liutikov16:04:41

Question: Is there a way to run a side effecting code when a subscription changed, such that rendering is not blocked by subscription evaluation?

p-himik16:04:04

Yes, via dispatch called from reg-sub-raw. But use it only if you absolutely must do that from a sub, for some reason. More details here: https://day8.github.io/re-frame/Subscribing-To-External-Data/ (notice how the very first section says that the document will be retired, and for a good reason). Another approach is to go through a proper event -> event handler -> effect -> effect handler chain. If there can be multiple events that affect the result of the same sub, you can use a global interceptor.

Roman Liutikov16:04:02

another question: when a new return value of a subscription is the same as a previous one, does re-frame caches a new value or keeps an old one?

p-himik16:04:35

re-frame doesn't cache results, it caches reactions created by subscribe. Reagent does cache reaction results. It calls set! on inner cache regarless of the value, but it calls watchers (including the re-rendering machinery) only when (= state old-state) is false.

kennytilton17:04:37

Is there a known phenomenon where a view rendering function with multiple subscriptions can run one time and not have the data it needs to show, then get that data in a second invocation after the data loads and render it, but the second rendering does not take effect? ie, the data never shows? btw, if we horse around with js/timeout and delay one event or another the same code runs fine, getting multiple renders still, but the data appearing? Sorry, I know that's a lot! 🙂

kennytilton17:04:59

The real mystery ^^^: a render with new data that gets discarded/ignored. Thx!

p-himik17:04:41

It'd be curious to debug it if you have a minimal reproducible example.

kennytilton19:04:08

I know, right! 🙂 Well, I am seeing spaghetti spaghetti dispatches/subscribes in fairly unidiomatic re-frame, so my first step is to clean that up and see if that cures it. Right now the trigger action dispatches two event-dbs and the rendering logic dispatches an event-fx to do an http-get that chains to an event-db setting state to which the renderer is subscribed. gasp :)

p-himik19:04:54

> the rendering logic dispatches an event-fx What do you mean exactly by the "rendering logic" here?

Lu20:04:21

Hope that’s not happening from the reagent view 😬

kennytilton23:04:07

Sorry, @U2FRKM4TW, my r/f terminology is weak... digdigdig...OK, the "view function", domino 5, the one generating the Hiccup, aka @UE35Y835W's worst nightmare. It looks like``` (defn myview [] (let [ ... subscriptions to data] (fn [] (let [_(dispatch [:trigger-data-loads]] <a couple of sub derefs>] [:span "What could go wrong?"]))) I do not do much re-frame, but I have alarms going off. And now I seem to be the resident r/f expert. :)

p-himik23:04:58

Oh yeah, don't do that. Dispatch that data loading event right where you make the decision to show that view. Use a global interceptor or a common function if there are many such places.

kennytilton23:04:55

Thx for the confirm! The good news is that the data load serves just this one (modal) view. I am considering an event cascade from http-get to app-db-storage, storing also the "show-modal" flag as true and then have the view watch the data and show-modal flag. Thx again.

👍 2
Lu09:04:19

How does a global interceptor address the issue? Say there are a total of 15 events and the get request needs to be dispatched from 10 of them. Would you have some conditions in the global interceptor to know whether the event in question is one of those 10 so you can make the get call?

p-himik09:04:01

Shift your perspective towards views. There's a view that needs some data. The view is displayed only after some subscription returns true. So, you simply want to load that data as soon as the value of that subs is true. For that, you create a global interceptor and run the sub function there, comparing the values between the old and the new app-db values. Of course, ideally the sub function should just use get-in or something just as simple, since the global interceptor will be run on every event. So, when the value changes from false to true, the interceptor just dispatches an event that loads the data. That's it.

👍 2
Lu09:04:21

Oh yeah! I got it I was thinking the side effect needed to happen in the interceptor that’s why I was confused 🙂