Fork me on GitHub
#re-frame
<
2021-08-13
>
Brandon Olivier20:08:48

What’s the best way to handle a situation where I need to make multiple requests each of which are dependent on the previous requests data?

ribelo20:08:26

add and handle on-success.

Brandon Olivier20:08:53

That was my intuition, but that’s going to force me to get them all in reverse,

Brandon Olivier20:08:03

I wouldn’t be able to only ::fetch-user

Brandon Olivier20:08:12

not without creating another effect

ribelo20:08:19

if you are using re-frame-http-fx then you still need to do this

ribelo20:08:04

in fact, even if you didn't use it, all you get with fetch is promise, which has to call another event in .then anyway

Brandon Olivier20:08:36

So, the situation I’m running into is something like fetching widgets belonging to the current user, but if that user data isn’t loaded yet, I need to fetch it first

Brandon Olivier20:08:15

In the setup you’re describing, I think, I’d have to have ::fetch-user-then-widgets and ::fetch-user or I wouldn’t be able to fetch the user without also fetching the widgets

ribelo20:08:04

problem begins when the data being fetched depends on the result of the previous fetch, then callback hell happens and there seems to be no good solution for this in re-frame

Brandon Olivier20:08:06

I can adapt that http-fx to take a more complex version of :on-success that I think will solve my problem.

Brandon Olivier20:08:16

That’s kind of what I’ve noticed.

Brandon Olivier20:08:13

What I’m thinking is adapting :on-success to take a vector of things to dispatch on-success instead of just those args.

Brandon Olivier20:08:49

That way I could call the root level event ::fetch-user-widgets but it would first call ::fetch-user then ::fetch-widgets

Brandon Olivier20:08:53

I think I can make that work.

ribelo20:08:34

I had this problem recently and solved it like this:

ribelo20:08:06

(rf/reg-event-fx
 ::fetch-address
 (fn [{:keys [db]} [_eid {:keys [user on-success] :as m}]]
   (if-let [user-id (get-in db [:x :y :z :users user :user-id])]
     {:http-xhrio {... something that uses user-id
                   :on-success (conj on-success [...])}}
     {:fx [:dispatch [::fetch-user {:user user :on-success [::fetch-address m]}]]})))

ribelo20:08:34

if you have the data you need in the db, you do a fetch, if not, you fetch the data you need and go back to current event in the on-success

Brandon Olivier21:08:07

That’s good. I might be able to get away with that, but it’d get messy at 3 layers of fetching 😓

ribelo21:08:51

good old cps

ribelo21:08:42

it's clojure, everything can be done with macro

ribelo21:08:05

but it's really not that bad, I also have a few layers of fetch and it's quite verbose, but it works and still follows the re-frame philosophy

ribelo21:08:39

if it hurts you too much, you can always use core.async in event and return nil

ribelo21:08:51

then you always have only two layers

ribelo21:08:58

(rf/reg-event-fx
 ::fetch-xyz
 (fn [{:keys [db]} [_eid {:keys [user on-success] :as m}]]
   (go
     (let [user    (<! (fetch-user user))
           address (<! (fetch-address user))
           xyz     (<! (fetch-xyz address))]
       (rf/dispatch [::fetch-xyz-successful xyz])))
   nil))

ribelo21:08:27

it goes completely against the re-frame philosophy, but it works

emccue23:08:32

@UJL94RYSW If you really want to minimize events you can make a state machine

emccue23:08:38

[:map 
  [:user-info-status 
    [:enum :not-asked
           :loading-widgets
           :failed-to-load-widgets
           :loading-user-record
           :failed-to-load-user-record
           :success]]]

emccue23:08:48

say your state looked like that

emccue23:08:56

you can have an event that loops back on itself

emccue23:08:38

(rf/reg-event-fx ::advance-state (fn [{:keys [db]} [_ info]] {:fx [[:http-xhrio (if (... (= status :not-asked) ...{:uri "/...request-widget.../") ...

emccue23:08:00

but truly its probably best to just manually write out every situation

emccue23:08:24

you make your first request due to "user interaction A" - event is called ::user-interaction-a

emccue23:08:40

on success you move to ::successfuly-fetched-widgets

emccue23:08:49

on failure you move to ::failed-to-fetch-widgets

emccue23:08:16

then in ::successfully-fetched-widgets you make another request to fetch the user

emccue23:08:31

and move from there to ::successfully-fetched-user or ::failed-to-fetch-user

emccue23:08:19

the duplication you will have between ::user-interaction-a and ::successfully-fetched-widgets is the specification of the request to send - assuming you check if you already have widgets at that stage

emccue23:08:31

but you can always make a function for that

emccue00:08:06

(defn fetch-user-request [...]
  {:uri "/api/.../user/..."
   ...})

(rf/reg-event-fx
  ::user-interaction-a
  (fn [{:keys [db]} _]
    (if (already-have-widgets? db)
      {:fx [[:http-xhrio (fetch-user-request ...)]]}
      {:fx [[:http-xhrio (... fetch widgets ...)]]})))

...

(rf/reg-event-fx
  ::successfully-fetched-widgets
  (fn [{:keys [db]} [_ resp]]
    (... {:fx ... (fetch-user-request ...) ...})))

emccue00:08:13

if that makes sense

emccue00:08:10

my position is to {:fx [:dispatch [::fetch-user don't on this

emccue00:08:12

in my mental model events aren't "helper functions"

alpox11:08:09

Just as an additional note of what I ran across sometime - kee-frame has a reg-chain to make this a bit less painful. Something like this may also be a neat solution? https://ingesolvoll.github.io/posts/2018-04-01-learning-kee-frame-in-5-minutes/#event_chains I never used it myself though

alpox11:08:50

Looks like that is now its own small library https://github.com/ingesolvoll/re-chain

Brandon Olivier20:08:34

for instance (dispatch [::fetch-user]) then (dispatch [::fetch-specific-user-data (:id user)])