Fork me on GitHub
#re-frame
<
2016-10-06
>
danielcompton02:10:18

I'm working on this today

danielcompton02:10:48

for the context of a single API call validating an input field, I'm keeping the state in a ratom along with a few other bits of info

len08:10:20

@superstructor: we use a :loading as a count of requests in progress

superstructor08:10:45

interesting, thanks everyone. @len what do you do to ensure the count is accurate; e.g. different paths or chains of multiple handlers causing requests / many failure & success handlers etc ? I guess you could do it in the http fx handler itself ?

len08:10:48

@superstructor thats the big tricky problem isnt it 🙂

len08:10:49

re-frame gives you the mechanism, but ensuring that the data you have is meaningful is up to the application

superstructor08:10:04

my only thinking on it so far, which I have not verified in any way, is to add a :num-in-flight or similar value feature to the http fx handler, so you can receive that somehow and do what you want with it. If it is tracked internally in one place, I guess it is less likely that someone will forget to update the loading count in their success/failure handlers ?

superstructor08:10:55

still that has a problem that would give no context to what requests are in flight, so if you have two unrelated parts of the page loading it might not be very useful :thinking_face:

len08:10:40

yes thats the thing that om.next / grapql approaches. you try to collect all requests together on the client into one

mikethompson08:10:09

Although the docs focus on app startup, it can also work for coordinating multiple async tasks at other times too.

mikethompson08:10:13

Attempts to use something like :num-in-flight counters are, at core, attempts to create little FSMs.

mikethompson08:10:48

re-frame-async-flow-fx effectively creates a FSM too: 1. it internally maintains state about "what has happened so far" (what events have already been seen) 2. it uses further events as triggers to drive the FSM forward towards success states or failure states

mikethompson08:10:54

It is all about state and events :-)

plexus11:10:33

is the current recommendation to define event handlers in events.cljs instead of handlers.cljs ?

plexus11:10:34

if so, can the leiningen template be updated? I'm writing a #lambdaisland episode on re-frame, and I'd hate it to be out of date already 🙂

mikethompson11:10:40

We've made that change already but it might not be released

plexus11:10:17

alright, I'll just pretend it is then 🙂

mikethompson11:10:26

Here we go: https://github.com/Day8/re-frame-template/pull/34 the change will be in the next release

mikethompson11:10:00

BTW, the other area in which our docs are lagging is around subscriptions

plexus11:10:39

you mean the magical sugar stuff?

plexus11:10:43

(reg-sub
  :all-complete?
  :<- [:todos]
  (fn [todos _]
    (seq todos)))

mikethompson11:10:48

This is the best document currently available on the new reg-sub capability: https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs

mikethompson11:10:54

Yep, you got it

plexus11:10:56

I'll do my best, the first episode won't go very deep, I'll focus on specific aspects in later episodes

joshkh11:10:14

Is there a way to abort a handler within an interceptor? I see ways of pre/post manipulating the data but not a way of stopping subsequent dispatches.

mikethompson11:10:17

Are you after debouncing ?

mikethompson11:10:06

Oh wait. i just reread your first sentence

joshkh11:10:30

I'm looking to validate the input with Spec and stop any updates if the data isn't right. I can do that within the handler functions themselves but that means a lot of repeated "Return db or the db in the world map"

joshkh11:10:59

It'd be nice to handle that in an Interceptor, even just for fun

mikethompson11:10:55

So there is a way for an interceptor to pretty much abort the further processing

mikethompson11:10:21

Notice the :queue and :stack

mikethompson11:10:35

Within an interceptor you can manipulate them

mikethompson11:10:24

99.9% of the time you don't touch them ... you just let the interceptor chain "run"

mikethompson11:10:59

But, if an :after changes the :queue it will change what further interceptors run

mikethompson11:10:19

The :queue is what interceptors are yet to run

mikethompson11:10:46

(def  stop-chain 
    (re-frame.core/->interceptor 
        :id  :stopper
        :before  (fn  [context] 
                      (assoc context :queue [])))

joshkh11:10:47

So a :before function could potentially clear out the queue?

mikethompson11:10:14

Yes, sorry, :before (not :after as I said above)

mikethompson11:10:12

Note: the :stack of interceptors (already run) would be unwound

mikethompson11:10:50

So if you wanted to be really brutal about stopping everything you'd clear BOTH the :queue and :stack

joshkh11:10:38

Okay, that sounds exactly like what I need, even the stack unwinding. Thanks for the explanation. On a side note, is it possible to pass parameters to interceptors when they're "attached" to a handler? All the examples I've seen have a vector representing the chain but that's it.

joshkh11:10:52

(reg-event-db :some-handler [stop-chain-interceptor my-spec] (fn [db....

mikethompson12:10:11

You'll want ...

joshkh12:10:19

Righty-o, I'll browse through

mikethompson12:10:25

... actually look at path

mikethompson12:10:41

It allows you to do this:

(reg-event-db 
   :some-id 
   [  (path  [:here :and :there]) ]     ;; <--- I'm assuming you want something like this
   (fn [_ _]
      ....))

joshkh12:10:22

Ah right, perfect!

mikethompson12:10:45

We call them interceptor factories

mikethompson12:10:03

They are a function which returns an Interceptor, who's :after and :before close over the args passed in

joshkh12:10:58

Great, thanks for the help. I'll give it a go.

mikethompson12:10:45

I'll give you a correction to that stop-chain above ... give me a minute ...

mikethompson12:10:10

(def  stop-chain 
  (re-frame.core/->interceptor 
    :id   :stopper
    :before  (fn  [context] 
                (-> context 
                    (dissoc  :queue)
                    (re-frame.core/enqueue []))))

joshkh12:10:09

That being the more brutal version?

mikethompson12:10:17

:queue is actually meant to be a Queue

mikethompson12:10:26

Not simply a vector

mikethompson12:10:18

Yeah, there should be a better API for clearing the queue. But that's the best I'm got for the moment

mikethompson12:10:41

(further adjustment made to the above code)

joshkh12:10:29

Noted 🙂

joshkh12:10:04

Works like a charm. Thanks again.

Niclas13:10:11

Is there any way in re-frame to subscribe to a subscription outside of a component? In my case the app needs to update the push notification configuration of the underlying platform whenever certain parts of the database are updated. I could make it so that each event handler that performs these database updates also directly update the notification configuration as a side effect, but it would be a much better fit imo if I could register subscriptions on the side that do the notification updates independently.

mikethompson13:10:54

@looveh I get this request a bit. Almost an FAQ. The re-frame way is that event handlers cause state changes, nothing else.

mikethompson13:10:09

Or, to be more correct, either the event handlers or their interceptors

mikethompson13:10:19

I do understand the initial attraction of having something "subscribed" to app-db which then causes further side effects. But re-frame is only reactive around the axis of v = f(s) (view is a function of the state).

mikethompson13:10:37

Instead of thinking subscriptions, you should put an interceptor on all event handlers which might cause the app-db changes

mikethompson13:10:45

That interceptor can see if a change has been made and, if they have, can then trigger the necessary further effects.

Niclas13:10:21

@mikethompson All right, I can see your point, thanks!

mikethompson13:10:16

Have a look at the on-change Interceptor

mikethompson13:10:40

It might not be exactly what you want but it will be indicative. You can then writ your own.

amacdougall17:10:09

Application architecture question: I'm making a hobby game, and I want to keep game logic separate from presentation. I am implementing the game state as an atom, and game state changes as transitions on it, but I want to keep it all in CLJC files, have it use plain old atom, etc, so I can run tests in Clojure and possibly use the logic outside of the web. I'm not entirely sure how to incorporate this into a re-frame application. I guess the smartest approach is not to tie the game logic to the atom type, but instead to write game logic which just deals in game-state hashes. Then a re-frame app could start with something like:

(def app-db
  {:game (game/initialize), :ui {}})
;; the :ui hash is just a catch-all for non-game-related webapp state
...while a Clojure app could say...
(def game-state (atom (game/initialize)))
...and tests would all just say...
(let [game-state (game/initialize)]
  (testing "some behavior"
    (is (correct? (transition game-state somehow)))))
...I think I may have answered my own question. I guess I'll hit enter in case anyone has better advice!

micmarsh17:10:24

idk if it's a proper way of doing things, but in my latest application I've offloaded nearly all the the handler logic to a seperate cljc file of pure functions (including fx, handled monadically)

micmarsh17:10:50

that way, basically every piece of business logic is a a pure function, testable in Clojure

micmarsh17:10:02

then you just get fancy with reduces if you need to test multiple actions over time

si1417:10:35

@superstructor re: requests in flight: this is exactly what I did, with an exception of tagging every outgoing request with UUID so I can track which request comes back

si1417:10:32

I'm wondering what's a better way to handle routing with bidi. Context: you have some Foos that have some Bars, so URLs are like /foo1, /foo1/bar1, /foo2/bar2, etc. I have two options in mind: A) define a static and fairly simple routing table like ['/' {[:foo-id {[:bar-id ''] :bar}] :foo}] and handle non-existing foo-ids and bar-ids in handlers (fire a request to a backend, check if foo and bar exist, etc.) B) on webapp start pull in a general structure of foos and bars, form a routing table, initialize bidi, perform a match and then do business as usual

si1417:10:11

I hope it's a clear explanation of a problem. What do you think? In my particular domain it's possible to pull a full list of Foos and Bars, so B is feasible.

mattly18:10:24

if I weren't also using bidi for server-side routing I'd go with https://github.com/funcool/bide instead

mattly18:10:44

specify routes as strings, none of this fancy-pants tree bs

mattly19:10:26

@si14 what I've done in the past with other frameworks is to basically make a polymorphic route handler for the root type