Fork me on GitHub
#re-frame
<
2017-05-08
>
mikethompson00:05:06

I'm assuming you have

(re-frame.core/reg-sub 
   :name 
   (fn [db _]
     (:name db)))

lingeeal00:05:24

but all my subs are called when I the db changes

mikethompson00:05:48

All the ones watching app-db directly, anyway

mikethompson00:05:21

While layer 2 subs might re-run whenever app-dbchanges, the further propogation will be pruned if the value is = to last time

mikethompson00:05:34

Read the text in that infographic

lingeeal00:05:38

then this does not have any benefits when rerendering the components, right? When using raw reagent atoms, only the components that use that atom get called when it changes. Now every time db changes, every component that has subscribed to parts of the db gets called

mikethompson00:05:04

Please have a read through the infographic. it explains.

mikethompson00:05:43

And remember that in cljs an = test is always shortcut if the two args are identical?

kamituel07:05:05

Hi! I’ve started using re-frame very recently (been using Reagent professionally for 1.5y, though). I think I grasp most of the concepts by now - most are really simple anyway, and the docs are really impressive! I’m using re-frame in my small pet project, just to learn in practice, and this project is essentially a table of contents for a number of magazines. The whole “database” for it is kept in a plain vector at the moment. I need it in my subscriptions handlers to pick a subset of all articles (based on user supplied search criteria) so that UI can render a list of results.

kamituel07:05:03

And so the thing I’m not sure how to handle best in re-frame: should I just assoc the whole “article DB” (a vector) to my db? Or should I keep it outside of db (in a separate namespace) and use it in my sub handlers?

kamituel08:05:25

Here is how I see it: Approach 1 - keep list of articles in the DB: lets me keep sub handlers pure, but I do assoc this big vector to my DB, even though I don’t ever want to alter it in any way - it’s supposed to be read-only. Approach 2 - keep this list outside of DB: lets me keep my state nice and small, I don’t pollute it with data that is supposed to be immutable. But to access this list of articles in my subs handlers, I need to make them not pure in a way.

kamituel08:05:53

I can solve this issue for event handlers by using coeffects, but we don’t have a notion of coeffects for sub handlers, do we?

kamituel08:05:30

The only place in docs I found that talks about it is here: https://github.com/Day8/re-frame/blob/master/docs/Subscribing-To-External-Data.md#some-code - it’s not exactly my case though, as in my case I’m not talking to the dynamic DB that can change overtime.

mikethompson08:05:45

Generally, you put all your data in app-db

mikethompson08:05:42

There's no advantage to "keeping my state nice and small"

mikethompson08:05:03

At the end of the day all that state has to live somewhere

mikethompson08:05:24

Make it one place - app-db

mikethompson08:05:40

Then your subs can get it And your event handlers can change it Etc

kamituel08:05:49

Thanks @mikethompson Yeah, that’s my current approach - I started looking into ways of removing it from the app-db for two reasons: (1) clairvoyant+tracer get very slow with such a big app-db (2) it doesn’t seem right to keep static data in the state (I might for instance ancidentally override it).

kamituel08:05:27

And at first, being a re-frame noob, I was “yeah, I’ll just use coeffects!” but then I realized I need this stuff in the sub, not event handler 😉

mikethompson08:05:16

Do only subs ever need this static data, or do event handlers need access too?

kamituel08:05:25

At the moment only subs do.

mikethompson08:05:58

You can do this then ...

mikethompson08:05:16

1. create a ratom whcih contains the static data called say r

mikethompson08:05:28

2.

(reg-sub 
  :static 
  (fn [_ _]  r)     ;; input signal is that ratom
  (fn [value-in-r _]  value-in-r))
Now any downstream subscription can (subscribe [:static])

kamituel08:05:30

Hmm, that’s a cool idea! Let’s me keep this one place dirty, and all other places are just typical sub handlers.

mikethompson08:05:01

3.

(reg-sub 
    :sorted 
    :<- [:static]
    (fn [static v] 
         ... do what you want with static 
         ))

kamituel08:05:18

And if I ever change mind, moving it back to app-db would be very simple too.

mikethompson08:05:36

Yep, just rewrite [:static]

mikethompson08:05:33

So that's a way of doing what you want BUT your situation is a little unusual. Generally, just keep stuff in app-db :-)

kamituel08:05:22

Exactly. It’s essentially “coeffects for subs” I was looking for, just not as explicit. Thank you @mikethompson 🙂 Yeah, I realize it’s a bit unusual scenario. For now it’s just a pet project, so I want to keep it all in memory, but if it grows larger, I might actually use local DB, or backend, to store all that.

kamituel08:05:51

In which case I’ll know what to do - it’s all in the docs 😉

oliy10:05:51

Hi re-framers, we are simplifying tests by using re-frame-test but ran into some tests cross talking because it seems that not all events are dispatched and handled within the test they originate from but survive the restore-state-fn and get handled while the next test is running (but the app-db has been reset)

oliy10:05:26

Are there any tools in re-frame or re-frame-test that will help us wait for the event queue to be drained before we finish the test?

oliy11:05:11

Current tactic is to have, at the very end of the test, a dispatch of ::test-complete and then a wait-for ::test-complete which presumably works because it goes to the back of the queue and when it is processed everything else has already been handled too

gamecubate17:05:07

Something I’m not clear about. Is it a no-no! to have side-fx within an :initialize event handler?

gamecubate17:05:34

OK. Got the answer (a solid yes) by looking at the todomvc example.

akiroz18:05:48

generally speaking: no side-effect in any event handlers but I usually cheat during development 😛

akiroz18:05:29

especially when testing 3rd party libs with stateful APIs

gamecubate18:05:48

@akiroz or stateful objects such as physics engine simulated worlds

gamecubate18:05:48

Which is why I was asking about side-fx. Wanting to reset the (physics engine) world, cancel requestAnimationFrame and setInterval IDs whenever user selects a new testbed assembly. Hence wanting to re-initialize the app db (via (rf/dispatch) with all those side-effects.

gamecubate18:05:59

Plus, optionally, incorporate in the returned db hash-map some kvs for interceptors.

danielcompton18:05:39

@oliy can you open up an issue on the re-frame-test repo with an example?

gamecubate18:05:42

Sorry for cross-talk. Thought I’d post my preflight + initialize code. If any of this looks bad, let me know.

;; -- Handlers -------------------------------------------------------------
(rf/reg-event-fx
  :preflight
  (fn [cofx _]
    (when-let [db (:db cofx)]
      (let [{:keys [raf timer-ids]} db]
        (when raf
          (.cancelAnimationFrame js/window raf))
        (doseq [id timer-ids]
          (.clearInterval js/window id))))
    {:db db}))

(rf/reg-event-fx
  :initialize-db
  (fn [cofx [_ f]]
    (let [{:keys [db]} cofx]
      {:db (f)
       :dispatch-later [{:ms 500 :dispatch [:start-physics]}]})))

(rf/reg-event-fx
  :start-physics
...

;; -- Entry Point -------------------------------------------------------------
(defn load-demo [name]
  (rf/dispatch-sync [:preflight])
  (rf/dispatch-sync [:initialize-db #(initial-state (name->demo name))]))

(defn run []
  (let [el (.getElementById js/document "container")]
    (load-demo "demo-1")
    (re/render-component [root-view] el)))

akiroz18:05:17

@gamecubate I don't think it's actually possible to get a cofx map what doesn't have a :db (unless you have an interceptor that deletes it or something....)

gamecubate18:05:04

Thanks @akiroz My mistake. Was actually observing same in my app crash log. Editing code…

akiroz18:05:34

I idealy you'd do all that side-effecty thing using a re-frame fxs if you have a lot of ordered tasks to do on init there's a library for bootstraping the init process using a declarative syntax.... let me find that...

gamecubate18:05:56

@akiroz thanks. This is very interesting.

akiroz18:05:59

Oh wait, I just realized that all your stuff is completely synchronous, you just need to create a single fx to do all that stuff and then dispatch the :start-engine event

akiroz18:05:01

let me write some code....

gamecubate18:05:39

Perhaps something like this:

(rf/reg-event-fx
  :initialize-db
  (fn [[{:keys [db]} _] [_ f]]
    (let [{:keys [raf timer-ids]} db]
      (when raf
        (.cancelAnimationFrame js/window raf))
      (doseq [id timer-ids]
        (.clearInterval js/window id)))
    {:db (f)  ; f returning the hash-map for a specific (physics testbed) demo
     :dispatch-later [{:ms 500 :dispatch [:start-physics]}]}))

gamecubate18:05:48

If correct, I have a tougher question yet…

akiroz18:05:41

note fx handlers vs event handlers that can invoke fx handlers

akiroz18:05:17

@gamecubate updated snippet, I messed up the event handler signiture~

gamecubate18:05:28

@akiroz Sweet. I like the separation of duties that fx introduces, leading to simpler code.

gamecubate18:05:12

A lot to read on re-frame, hadn’t reached reg-fx and interceptors. Suspected key to simpler code lied therein.

akiroz19:05:16

yep, really great stuff~ and all the better to read about it now that the docs resembles a little guide book with pictures and stuff 😄

akiroz19:05:40

I really do miss the way re-frame does things now that I got dropped into a Redux project in my day job..... trying to do this stuff without immutable data structures and the usual clojure manupulation fns is like trying to juggle while riding a unicycle....

gamecubate19:05:46

I’ve updated my code, basing myself on yours:

(defn box
  "A box of half-dimensions hw and hh"
  [id cx cy hw hh]
  [{:type "box" :id id :cx cx :cy cy :hw hw :hh hh :body-opts {:angle (u/radians 12)} :fixt-opts {:restitution 0.7} :view-opts {:label true}}])

(defn two-boxes [w h]
  (concat
    (box "box-1" (/ w 2) (/ h 2) 10 10)
    (box "box-2" (/ w 2) (+ 50 (/ h 2)) 10 10)))

(defn rig-loader [f w h]
  (let [hw (/ w 2)
        hh (/ h 2)
        world (pl/world [0 10])
        rig (f w h)
        _ (pl/assemble-in! rig world)]
    {:world world
     :bounds [0 0 w h]
     :raf nil
     :timer-ids []}))

(reg-fx
  :cancel-raf
  (fn [raf]
    (when raf
      (.cancelAnimationFrame js/window raf))))

(reg-fx
  :cancel-timers
  (fn [timer-ids]
    (doseq [id timer-ids]
      (.clearInterval js/window id))))

(reg-event-fx
  :init
  (fn [{{:keys [raf timer-ids]} :db} [_ loader]]
    {:db (loader)
     :cancel-raf raf
     :cancel-timers timer-ids
     :dispatch-later [{:ms 500 :dispatch [:start-physics]}]}))

(rf/reg-event-fx
  :start-physics
  (fn [cofx _]
    (let [db (:db cofx)
          raf (.requestAnimationFrame js/window #(rf/dispatch [:tick %]))]
      {:db (assoc-in db [:raf] raf)})))

(rf/reg-event-fx
  :tick
  (fn [cofx [_ dt]]
    (let [db (:db cofx)
          {:keys [world raf]} db]
      (pl/step world)
      (.cancelAnimationFrame js/window raf)
      {:db (assoc-in db [:raf] (.requestAnimationFrame js/window #(rf/dispatch [:tick %])))})))

(defn run []
  (let [el (.getElementById js/document "container")
        w (.-clientWidth el)
        h (.-clientHeight el)])
  (dispatch-sync [:init #(rig-loader two-boxes w h)])
  (render-component [root-view] el))

gamecubate19:05:51

@akiroz I gather you don’t feel comfortable riding a unicycle. 🙂 Question of time.

gamecubate19:05:56

I have a friend who is coding for react full-time (no redux) in JSX. He loves it and looks at me enigmatically whenever I try and explain re-frame.

gamecubate19:05:11

To each his own.

gamecubate19:05:51

React being a giant leap ahead of react-less coding in JS.

akiroz19:05:25

@gamecubate why not just call rig-loader inside run instead of passing it into the init handler?

akiroz19:05:18

^^ definitly true... but it just feels odd to be using redux/react while trying to be the least function as possible.... I dunno...

gamecubate19:05:16

Because I want riggers to be pure functions without side effects. They return a specification for bodies, their shapes and location in some bounded area.

gamecubate19:05:21

The rig-loader OTOH knows to make a new (physics engine) world, integrate the rigs into that world, and know what to return to make re-frame event handlers happy.

akiroz19:05:20

Hmm... I'm not entirely sure how the rendering layer works but maybe there should just be fxs for affecting the world?

gamecubate19:05:12

Actually the :start-engine handler is where stuff is happening.

gamecubate19:05:28

updating code to clarify…

akiroz19:05:54

I think you can just stick the whole rig-loader bit into the init handler since you're storing the world in the app-db and I guess everything that manupulates world is done via event handlers

gamecubate19:05:02

Hmm… Sounds like I could. My goal is to simplify for end user, letting him/her focus on rig definitions (what bodies, what joints, their parameters, their location and angle) as hash-maps, letting the framework (https://github.com/gamecubate/re-frame-physics) do the rest.

gamecubate19:05:24

It does sound like I don’t need to expose rig-loader indeed.

gamecubate19:05:18

A select element will let the user select a rig name. Upon selection, :init will be re-dispatched with the new

(name->rig)
rig.

gamecubate19:05:35

So yes, this looks good. Thanks for the code!

akiroz19:05:45

Ahhh nice, so it's a simulation thing 🙂

gamecubate19:05:46

Will modify a bit to allow for rig name param

gamecubate19:05:09

Simulation is always less risky than reality. That’s why I code for physics rather than throw myself off cliffs. 🙂

gamecubate19:05:49

Now coding for a way for rig-specification writer (end user) to add his dispatched event ids and handlers inside the spec with no prior knowledge of how the framework will handle it. Some :before-tick and :after-tick functions that will be added to the specification.

gamecubate19:05:12

Sounds like… interceptors. 🙂

gamecubate19:05:18

I better read up.

gamecubate19:05:02

(defn my-new-rig [w h]
  {:physics (two-boxes w h)
   :pre-tick  #(.log js/console :pre-tick)
   :post-tick #(.log js/console :post-tick)
   :every [1000 #(.log js/console "toc")]})

gamecubate19:05:09

Actually, not sure sheltering end user so much a good idea. This is after all based on re-frame.

gamecubate19:05:29

Instead of :every, may actually be worth their while to invest time in :dispatch-later and others…

gamecubate19:05:51

I need to rethink. Thanks for the help!

akiroz19:05:06

@gamecubate after looking at the code again, I think it would be better to push all the world-interacting code out of the re-frame system and only handle updating pure data entities within re-frame

akiroz19:05:37

each tick should take the pure data and push changes into the world

gamecubate19:05:11

To clarify, the physics engine moves the simulation forward one

(step world)
at a time. I was invoking this from within the :tick event handler. Because world is part of the db, it changes, thereby triggering view redraws.

gamecubate19:05:33

What you are saying is that (step world) and all other physics commands should be invoked outside of re-frame. As long as world is stored in db, redraws will take place but keeping physics out of the re-frame picture is better. ?

gamecubate19:05:28

It’s certainly possible.

gamecubate19:05:26

But one reason why I used re-frame for that was precisely to harmonize, regulate event dispatches and handlers.

gamecubate19:05:19

I need for example to change gravity to a random new one at reg intervals. I certainly could do that outside of re-frame, using setInterval. But then, I am dealing with events both inside of re-frame (ui events) and outside (physics affecting timer events).

gamecubate19:05:33

I felt this to be a recipe for trouble.

akiroz19:05:34

The world probably doesn't belong in the db either, I'm not sure if performance will allow this but idealy pure data structures would live in the app-db and you'd use a sub to update the world as event handlers manupulate the app-db

gamecubate19:05:23

Isnt’ a sub, conceptually, an extractor of data, rather than a modifier of it?

gamecubate19:05:34

extractor… reshaper…

gamecubate19:05:58

i.e., keep side effects in dispatch handlers

akiroz19:05:06

yes, subs normally extract data and pass it to views (which is not part of re-frame)

gamecubate19:05:36

performance so far has been more than satisfactory for my needs and I tend to choose to ignore that for now.

akiroz19:05:40

instead of passing data to reagent views you'd update the world instead

gamecubate19:05:13

but how will the db be manipulated such that, without triggering side effects from within subscriptions, reagent decides it is time to redraw the views?

gamecubate19:05:36

60 times per sec, world needs to be told to (step), and db needs to see some change.

akiroz19:05:40

I still haven't took my time to unnderstand how r/atoms actually work but they basically tracks functions (views) that dereference them and trigger a re-draw when they change.

gamecubate19:05:53

Doing it from one place seemed logical.

akiroz20:05:22

but in your case, it would just be setting a watch on the atom with the re-draw function

gamecubate20:05:29

reagent has a sweet

track [f]
that saved my (professional dev) life once I discovered it.

gamecubate20:05:23

You’re saying I would store my world into a ratom and do a

@(r/track get-world)
on that from within my views?

gamecubate20:05:52

Hmm… Why not keep the world inside re-frame then?

gamecubate20:05:01

And just subscribe to that?

gamecubate20:05:37

Are you thinking that re-frame was not designed for stateful stuff that changes outside of its control (as (:world db) would whenever (step (:world db)) was called?

akiroz20:05:16

no no, I'm thinking of having a static reference of the world in a def somewhere and then adding a watch to the app-db to track changes & re-render accordingly

akiroz20:05:11

yeah, something like that

akiroz20:05:37

since rendering to the world is actually a reactive process I thought it may make more sense to take it out of the proactive part of re-frame

gamecubate20:05:06

(def world
  (r/atom
    (pl/world 10 10)))

(defn get-world []
  @world)

(defn initial-state []
  {:watcher @(r/track get-world)})

akiroz20:05:06

might be easier to setup the system such that the world is always automatically synced with the entities inside the db

akiroz20:05:48

since world is a mutable js object, you can just do (def world (pl/world 10 10))

gamecubate20:05:54

How do you watch it? Needs wrapping in some [r]atom.

akiroz20:05:18

and actually.... you can just use a :update-world fx to do all the js mutating stuff

akiroz20:05:43

since tick is the only event that changes the world

gamecubate20:05:57

That’s what I did with

(rf/reg-event-fx
  :tick
  (fn [cofx [_ dt]]
    (let [db (:db cofx)
          {:keys [world raf]} db]
      (pl/step world)
      (.cancelAnimationFrame js/window raf)
      {:db (assoc-in db [:raf] (.requestAnimationFrame js/window #(rf/dispatch [:tick %])))})))

gamecubate20:05:25

I see. Do like you suggested for :init…

akiroz20:05:03

btw, how are you updating the world? It seem like the api expects you to iterate through entities in the world and mutate each one...

gamecubate20:05:03

Calling (pl/step world) triggers a physics simulation update. Because world is inside the re-frame db, it triggers re-draws via a sub which gets the list of world bodies. I pass that list to doseq, converting (world) meters into screen pixels, returning that to the view.

akiroz20:05:49

oh... it looks like I completely misunderstood the way plank.js works

akiroz20:05:59

I thought it handles rendering as well.... silly me.

gamecubate20:05:58

Nope. I render its bodies as reagent/re-frame driven SVG elements.

gamecubate20:05:00

You’ve been really helpful. It’s time I explored what’s been suggested so far (reg-fx looks yummy!). I will keep the world inside the db given the smooth performance thus far.

gamecubate20:05:04

Plus want to add in-browser rig specification parser (possibly via KLIPSE) to make this as dynamic as possible for the end user.

gamecubate20:05:12

So more trouble ahead… 🙂

akiroz20:05:18

In this case I think it makes sense to put world inside db as well and just treat it like an immutable thing with it only changing with the step method

akiroz20:05:39

Well, good luck~

gamecubate20:05:07

I’m glad you don’t disagree. Makes me feel safer in my choices (made less than 1 week ago). Didn’t know re-frame until 3-4 weeks ago so it’s a bit fo a gamble.

akiroz20:05:48

no problem~

timgilbert21:05:10

Say, is there a way to test whether a value I've got is dereffable? Trying to implement a component that can take in either a subscription and deref it, or a value and display it as-is

timgilbert21:05:52

Ah, looks like (satisfies? IDeref (atom "")) works

danielcompton23:05:23

@timgilbert this is what re-com does