Fork me on GitHub
#re-frame
<
2016-01-29
>
pdlug01:01:34

Is it considered back architecture to dispatch from within a subscription?

pdlug01:01:34

Case I’m trying to figure out the “right” way to handle is: search criteria changing in response to UI actions, every time the criteria changes I want to re-run the search via ajax so I’m looking for the right way to listen for changes to the criteria and kick that off

danielcompton01:01:17

@pdlug: take a look at dynamic subscriptions

pdlug01:01:03

ok, reading that page now

paulb06:01:07

is most of the info on that page correct? because there is a big warning at the beginning of it simple_smile

mbertheau08:01:52

Is there a meaningful difference between these two components:

clojure
(defn c1 []
  (let [s (re-frame/subscribe [:foobar])]
    (fn [] [:div <@U06QXASV8>])))

(defn c2 []
  [:div @(re-frame/subscribe [:foobar])])

mbertheau08:01:23

It is my current understanding that with c2 what happens is when the :foobar subscription changes, c2 re-renders, in the process subscribes again, which in turn calls the :foobar subscription function again. In contrast with c1 the changed value of the :foobar subscription is detected and used directly to re-render c1 without calling it a second time.

mbertheau08:01:36

clj
(defn c3 []
  (let [s (reaction (foobar @re-frame.db/app-db))]
    (fn [] [:div <@U06QXASV8>])))

mbertheau08:01:59

Now I think that c3 is equivalent to c1. I don't yet see the advantage of using subscriptions.

mbertheau08:01:19

Btw, I'm trying to figure this out because I have a performance problem. The turnaround time from hitting a key in an input field to react updating the value of the HTMLInputElement is over 100ms, which leads to lost keystrokes and a very jittery feel.

mbertheau08:01:12

clojure
(def s (reaction (foobar @re-frame.db/app-db)))
(def c4 []
  [:div <@U06QXASV8>])
Seems like the most straightforward way to me right now.

mccraigmccraig09:01:09

@mbertheau: what handler middleware do you have ? (when i had similar perf problems it turned out to be schema-checking the app-db on every event which was the problem)

mikethompson09:01:13

c1 and c3 are the same. So much so that I really don't see that there's be a performance difference between them.

mikethompson09:01:18

debug can be slow

mikethompson09:01:33

If it is diffing a big data structure

mikethompson09:01:51

Remember that's what debug does. It does a complete diff of the before and after

mikethompson09:01:12

Try removing it and seeing if performance suddenly improves

mikethompson09:01:34

debug should always be conditioned out in production

mikethompson09:01:06

(if ^boolean js/goog.DEBUG debug)

mbertheau09:01:41

@mikethompson: Thanks! How is js/goog.DEBUG set?

mikethompson09:01:31

If you are doing :advanced compilations it will be false

mikethompson09:01:37

If not, it will be true

mbertheau09:01:49

Ah. I'll try that and report simple_smile

mbertheau09:01:17

@mikethompson: Could you clarify the purpose of subscriptions vs. bare reactions as in c4?

mikethompson09:01:22

He pointed out the debug problem simple_smile

mikethompson09:01:16

Take your glory where you can !!!

mccraigmccraig09:01:20

@mbertheau: if you aren't using :advanced then you can also set goog-define vars in the cljs compiler options

mikethompson09:01:31

@mbertheau: c4 isn't often very good. You want those subscriptions/reactions to disappear when the component using them disappears. Hence use of form-2 versions.

mbertheau09:01:37

Anyone know why two subscribe calls with the same subscription don't share the same ratom?

mbertheau09:01:53

That would be an advantage of subscriptions vs pure reactions.

mikethompson09:01:31

I'd like to add that but I'm waiting on some changes to reagent

mbertheau09:01:36

Does reaction notice ratom derefs in a function that's called in the reaction form? I.e. (def f [] (:foobar @db)) (def reactive-f (reaction (f))) - is reactive-f updated if the db ratom changes? Or do I have to write (def reactive-f (reaction (:foobar @db)))?

mikethompson11:01:31

@mbertheau: yes, any deref of any ratom anywhere is noticed during the computation wrapped by reaction

hugobessaa17:01:25

hey people, where should I put my analytics events? trigger them from a middleware?

sooheon18:01:27

jaen: You know this semantic-ui dropdown example you gave me before? https://clojurians.slack.com/files/jaen/F0JKFP7T6/-.clj I’m having trouble adapting it for re-frame. Could you help me out a bit? I’m trying to keep each field in the form synced to a corresponding key in a foo-form-data map in the app-db, but I don’t know where to hook in the handler.

sooheon18:01:21

Dispatching it from the :onChange function doesn’t seem to work

jaen18:01:34

Ha, well it won't be that easy, I specifically wrote it to not use callbacks but atoms.

jaen18:01:56

Theoretically you could call it inside onChange, yes; I'm not sure why it doesn't work for you without seeing your code

jaen18:01:01

But I would suggest something else

jaen18:01:38

That is leaving the dropdown as-is and instead wrap that in a reactive wrapper.

jaen18:01:12

I use something like this - https://gist.github.com/jaen/c59a3e0abcf0c6f6ade2 - because reagent/wrap works only with values, not atoms/reactions.

jaen18:01:18

With that you could do

jaen18:01:46

(let [sub   (rf/subscribe [:your-query])
      value (wrap #(@sub) #(rf/dispatch [:update-sth] %))]
  (fn []
    [dropdown value choices]))

sooheon18:01:04

Ah this is the wrapper that you showed me before as well

sooheon18:01:10

and I noped out then ahha

sooheon18:01:20

So without understanding the internals

sooheon18:01:07

I can use it in that way—I think I can try it

sooheon18:01:24

Would the advantage of this over just adding the dispatch to onChange be that it is a two way thing? both the dispatch and the subscription are synced?

sooheon18:01:43

Because I’m not sure if I just add an onChange dispatch, what would happen if the data is changed elsewhere.

jaen18:01:27

In this case actually I think reagent wrapper would work as well:

(let [sub   (rf/subscribe [:your-query])]
  (fn []
    [dropdown (r/wrap @sub #(rf/dispatch [:update-sth] %)) choices]))

jaen18:01:55

Also take a look at previous snippet, I fixed it a bit

jaen18:01:01

Do you see the difference here?

sooheon18:01:04

yeah you made it a type2?

jaen18:01:17

The reagent wrapper has to be created inside the render function each time you render

jaen18:01:28

The one I wrote you can create in the outer function, once (per mount)

sooheon18:01:57

ahhh yeah I see the difference. so the reagent one, were I to put it in the let binding, would only be bound once.

sooheon18:01:15

But if you put it in the inner function, it would be reactive depending on the sub anyway

jaen18:01:29

What is the advantage? I think the advantage is consistency - that way the dropdown can always expect a reactive atom-like thing and you can just swap a normal atom or a cursor or such a wrapper

jaen18:01:46

Yeah with reagent if you put it in the outer function

jaen18:01:57

It would be created once with the value of sub while mounting the component

jaen18:01:03

And it wouldn't be reactive at all.

jaen18:01:27

At least that's how it was when I tried that before and why I wrote my own wrapper that can be created in the outer function.

sooheon18:01:31

I see. I’ll try it with the second method and the stock r/wrap

sooheon18:01:47

Thanks as always

jaen18:01:48

It's probably better that way until you encounter problems.

sooheon18:01:55

Yeap, simpler for me

jaen18:01:06

But at some point you will probably see that in some cases that way of creating a wrapper is not handy and then you can try mine; it's not too hard - it's basically just an atom-like thing that on deref calls the first function, on reset! - the other.

jaen18:01:19

But no need to go there until it's a problem for you.

richiardiandrea18:01:28

Guys, is anybody using dirac to have the nice handler/subs/views tracking in the browser console?

richiardiandrea18:01:53

I was wondering if I can use both cljs-devtools and dirac interchangebly

jaen18:01:30

They are not interchangeable

jaen18:01:35

They are separate things

jaen18:01:43

And dirac just requires cljs-devtools in your project.

jaen18:01:06

So you can either have dirac and cljs-devtools, cljs-devtools or none in your project and expect it to work.

jaen18:01:57

So if nice display of Clojurescript values in console.log is what you're after

jaen18:01:03

Then cljs-devtools is enough.

richiardiandrea18:01:14

oh ok, I am reading the README a bit more carefully sorry about that 😉

jaen18:01:42

No problem

jaen18:01:17

What dirac adds on top of cljs-devtools is basically a in-browser Clojurescript REPL and using custom formatters in the debugger as well, not only the console.

jaen18:01:42

So unless you don't need those you can limit yourself to cljs-devtools.

richiardiandrea18:01:08

Well I use the repl in cider as I love to have my keybindings available 😉 but the debugger addition can be handy

jaen18:01:59

Also always check your versions - different versions of dirac and it's browser plugin can break it.

jaen18:01:07

But when it works it's awesome.

actsasgeek19:01:52

ok, a few disclaimers. 1. I haven’t really done much web dev in a while and very little in Clojure and 2. I’ve looked a Hoplon, Re-frame, Luminus, etc. and this might be a obvious/stupid question. I’m not sold on the idea that I want or need an SPA but I like the FRP approach. I find the idea of an app-state a bit unnerving but the README.md talked me down off the ledge. So what I want is to do a multi-page app but have, say, page-db so that I can use FRP for all the AJAX-y things I would do in any webapp. Of course, I may change my mind later and move to an SPA. With that in mind, is it reasonable to combine, say, re-frame+luminus?

shaun-mahood19:01:03

@actsasgeek: I don't see why not, but it depends how you want to deal with state when you are changing between one page and another in Luminus. I've thought about doing a combination of the 2, and then for each page have either the same re-frame app and dispatching specific mini-SPAs or building separate re-frame apps for each page.

shaun-mahood19:01:29

Is that at all similar to what you're thinking of?

actsasgeek19:01:45

right, exactly. It’s all very hypothetical at this point, though.

shaun-mahood20:01:16

Try building a bit of what you need it using just re-frame, or at least think about it - it's pretty nice and should give you a pretty good idea of how to do what you want. I basically went with re-frame and built an SPA just so I wouldn't have to deal with the mismatch and it worked out really well for what is primarily a forms rich CRUD app that I could have built using HTML for the majority of the functionality.

sooheon20:01:45

jaen: for this example,

(let [sub (rf/subscribe [:your-query])]
    (fn []
      [dropdown (r/wrap @sub #(rf/dispatch [:update-sth] %)) choices]))
If the subscription returns a vector, and I want to iterate [dropdown] over each thing in the vector, how should it be structured?

jaen20:01:21

So result of sub is what, a list of dropdowns to show?

sooheon20:01:11

it’s a bit convoluted, but first yes.

sooheon20:01:04

it looks like [{:name foo :number foo :unit foo} {:name bar :number 12309 :unit foo}…]

sooheon20:01:29

And say I want a dropdown which would determine the values of :name, :number, :unit for each map

sooheon20:01:40

So three dropdowns each

sooheon20:01:45

n number of times

jaen20:01:25

Hah, that's slightly complex. Any gist of the subscriptions and event handlers you have?

sooheon20:01:26

Ok, more simply, I wonder if it’s ok to

(let [sub (rf/subscribe [:your-query])]
  (fn []
    (for [item @sub]
      [dropdown (r/wrap item #(rf/dispatch [:update-sth] %)) choices])))
, or should it I wrap the @sub?

jaen20:01:07

Well, it'll be a bit inefficient if you sub that way - update to any dropdown will re-render all.

jaen20:01:36

I think so at least. But it feels like it should work at least.

jaen20:01:49

Except you need to do (doall (for ...))

sooheon20:01:09

Ah yeah saw that in your example

jaen20:01:20

Because IIRC reagent can't deal with derefs in lazy sequences (unless that changed).

sooheon20:01:59

No I recall it not working without the doall

jaen20:01:12

Also another thing to consider is ^{:key "something-unique-for-a-row"}. It is not strictly needed, but it makes React's reconciliation job easier and it will emit a warning if you don't use it.

jaen20:01:27

(you have that in my example as well)

sooheon20:01:12

Along the same lines, it should be possible to

(let [sub (rf/subscribe [:your-query])]
  (fn []
    [dropdown (r/wrap (@sub 0) #(rf/dispatch [:update-sth] %)) choices]))
as well?

jaen20:01:59

for just the first element from the list returned by subscription? Sounds about right.

jaen20:01:26

Though one thing about your event - how will you know which element of the list to update?

jaen20:01:52

You should parametrise it somehow; I think you can do that with re-frame?

sooheon20:01:09

My :update dispatch function takes the list of keys to the value to be updated (like assoc-in)

sooheon20:01:27

Oh yeah speaking of which, is it correct that the % is outside of the dispatch vector?

jaen20:01:23

Hah, I don't remember, I've never used re-frame library directly, even though I was highly inspired by the readme

jaen20:01:27

But I think they can take params

sooheon20:01:05

It’s supposed to be the value to be dispatched right? I think the way I have my function it should be inside the vector. Thanks!

jaen20:01:51

Yeah, my mistake - it seems re-frame expects you to call it like (rf/dispatch [:some.event/name param1 param2 ...])

jaen20:01:56

So yes, it should be inside vector

sooheon20:01:22

Yeah haha don’t blame you if you’re just typing from memory

jaen20:01:32

So in your case that should've been something like #(rf/dispatch [:update-sth id %]))

jaen20:01:48

Because you need to somehow pass the id of the item you want to modify, right?

jaen20:01:54

I mean, the index in the vector

sooheon20:01:36

yeah it’ll look liek #(rf/dispatch [:update-fn [:foo 0 :bar] %)

actsasgeek20:01:40

@shaun-mahood: thanks for the suggestion. I guess my only thought was…I need a backend anyway and why not luminus, even if I don’t use everything.

shaun-mahood20:01:19

@actsasgeek: Oh I think Luminus is a good choice. I ran into some issues with it months ago and ended up moving to a less framework-y solution for my backend, but I often refer to it to see what libraries I should bring in and I think it's a fantastic project. The only thing I was thinking was that it might be worth trying to just build an SPA and see if you really need the separation you were thinking of - I re-wrote my first re-frame app once or twice as I learned things, and if I had segregated things initially I would have missed out on some of the things I found and wouldn't have had as much fun.

actsasgeek20:01:14

yeah, that’s exactly where I am. I’m like…well, maybe I should create a re-frame project and then a luminus project to see what it requires and uses.

actsasgeek20:01:40

but the truth be told, I probably just need a vanilla ring/compojure project in the end.