Fork me on GitHub
#re-frame
<
2016-08-08
>
fenton07:08:51

say you have two values in your app-db which both need to be set in order to do some action. These values can be set in any order. How do people deal with that type of situation?

escherize07:08:44

(-> db (assoc :a 1) (assoc :b 2)) ?

fenton07:08:10

@escherize: I'm not asking how to set two values in the db. I'm wondering how to trigger an action if and only if both values are not nil.

fenton07:08:50

kind of like a business rule. if balance is negative AND user has enough overdraft protection allow transaction... that kind of thing.

escherize07:08:28

I see. You can write a subscription for that or check in your event handler. Does it matter for anything besides that particular event?

escherize07:08:43

(that A and B are states a and b)

fenton07:08:56

it feels like in reframe i only ever have one value at a time....

escherize07:08:01

(reg-event
  :do-a-thing
  (fn [db _]
    (if (and (-> db :some :path)
             (-> db :another :path :somewhere :heh))
      (do-thing-to db)
      db)))
?

escherize07:08:25

if you want to disable the view when not (and (-> db :some :path) (-> db :another :path :somewhere :heh)), I'd reccomend a subscription

escherize07:08:53

(def-sub :my-condition
  (fn [db]
    (and (-> db :some :path)
         (-> db :another :path :somewhere :heh))))

;; this is the view:
(defn my-component []
  (let some-condition (subscribe [:my-condition])
       (fn []
         [:div ...
          (when @some-condition
            [:input {:type :button
                     :on-click (fn [e]
                                 (js/alert "i did it!"))}])])))

escherize07:08:19

Does that make sense?

fenton07:08:33

my action isn't a view oriented action...a backend action needs to occur....

escherize07:08:00

so then, does the reg-event approach above solve your issue?

fenton07:08:58

i've not used reg-event before...how/when does it get called?

fenton07:08:06

like i need to call it, and it will only (do-thing-to db) if conditions hold...but can't trigger by the setting of either of two locations?

escherize07:08:06

It's a bit of a rough time in re-frame. that would be the same thing as register-handler

escherize07:08:01

so, back to:

say you have two values in your app-db which both need to be set in order to do some action.  These values can be set in any order.  How do people deal with that type of situation?
what do you actually want to have happen?

fenton07:08:46

call a backend update function when both locations have a non-nil value.

fenton07:08:20

automatically on the setting of the variables...i want to react to the variables being set...

escherize07:08:37

by variable you mean something in app-db, right?

escherize07:08:38

(register-handler
  :maybe-call-backend
  (fn [db _]
    (if (and (-> db :some :path nil?)
             (-> db :another :path :somewhere nil?))
      (do (POST... )
         (assoc :loading? db true))
      db)))

escherize07:08:28

When those two palces get updated, I think I would put a dispatch into maybe-call-backend

fenton07:08:51

but i need to manually call that...i'm looking for something that gets triggered because the values are set...

escherize07:08:04

Yeah if you want to use add-watch

escherize07:08:20

that might be what you're after. I wouldn't really reccomend it though. 🙂

escherize07:08:35

(add-watch re-frame.db/app-db (fn [...] ...))

fenton07:08:12

why dont u recommend it?

fenton07:08:18

performance?

escherize07:08:07

to my mind, it may be difficult to keep track of what happens when. It may obfuscate the chain of causation a bit.

escherize07:08:42

performance might be an issue, but not my #1 concern.

fenton07:08:49

ok. currently I'm passing everything through a pub/sub core.async. Am planning to watch for variables, if i find them, check if the other is already set, if so do function...

fenton07:08:04

but i was wondering if i was re-inventing the wheel or something...

fenton07:08:54

but i find i have a lot of situations where a transition from one state to the next, depends on more than one variable to define the first state that is ready to transition to the next state so to speak.

fenton07:08:16

@escherize: thanks for being a sounding board...now i must really go to sleep! lol 🙂

escherize07:08:04

Sure, good luck with your pub/sub architecture 🙂

mikethompson08:08:12

@fenton: I think this sounds like the classic cross-cutting issue that's solved best by middlware

mikethompson08:08:29

Sounds like there are n event handlers which might update m pieces of state (m == n?). And decision d is a function of those m bits of state?

mikethompson08:08:28

In which case, you'd normally wrap each of the n handlers in a piece of middleware which looks at the m pieces of state, and make the necessary d

mikethompson08:08:43

The on-change middleware interceptor is an example of this pattern

sam.roberton09:08:01

@mikethompson: fyi, I've found a bug in alpha10. The path interceptor doesn't allow for the possibility that it might be intercepting an FX handler which doesn't return a :db effect. std_interceptors.cljc line 171 does an unconditional assoc-effect of full-db into the context, when actually it's possible that your FX handler returned other effects but no change to the DB. The end result is that the :db fx handler sees a :db effect, but with a nil value, when it should see no :db effect at all. This unfortunately causes your app-db to be wiped out, rather than left alone 😞

(reset! re-frame.db/app-db {:quux "don't delete me!"})

(reg-event-fx :foo [(path :quux)] (fn [db _] {:dispatch [:bar]}))
(reg-event-db :bar (fn [db _]
                     (println ":bar event db: " (pr-str db))
                     db))

(dispatch [:foo])

;; prints => ":bar event db:  {:quux nil}"
Without the path interceptor it does the right thing.

sam.roberton09:08:32

Well, rather than me just complaining about it, this is probably more useful: https://github.com/Day8/re-frame/pull/189 Note that there is a crappy downside to this solution: if you do return {:db nil} from an event handler with a path interceptor, this will now consider that a no-op rather than wipe out that bit of the path. I've done it that way (a) because that's consistent with what the debug middleware reports, and (b) because the interceptors namespace doesn't expose an ability to differentiate between a {:db nil} and a map with no :db key at all. But I actually think it should. Returning {:db nil} from a handler with a path interceptor seems like an eminently sensible way for a namespace to clean up after itself when the user's done with it.

mikethompson09:08:33

Yeah, i see what you mean

johanatan16:08:28

Can someone please answer my question regarding keys on h-box children of a v-box?

fenton16:08:00

@mikethompson: yes totally agree that this should be a middleware type thing. Is there a middleware hook of sorts? I want to hook in AFTER the event dispatch completes it's update of the app-db, since I'll be querying that. i cant use subscriptions because they are only UI focused and dont get run if not associated with some UI. What I'd like is a non-UI subscription. Perfect would be able to subscribe to multiple bits of data in the app-db.

fenton17:08:47

maybe after would be the ticket. It takes a function and the app-db. Runs the function passing in the state of the app-db after each and every dispatch occurs. Is that correct?

shaun-mahood19:08:02

@johanatan: I just opened one of my existing apps using re-com and added ^{:key "my-key"}" to a v-box that has h-box children, and it appears to work fine for me when I inspect the elements. Shows up with react-id of the form data-reactid=".b.3.$my-key.0" on the parent and children, works for me with both re-com 0.6.1 and 0.8.3 so looks like nothings changed for that over the past year or so, at least for re-com (all the other dependencies are about a year old).

wasser19:08:36

@johanatan: I just looked also. I see the keys, but I needed to use the “React” plugin to Chrome. The regular inspector didn’t show them. (And I don't I see them in Safari). But they ARE needed. React throws an error and a warning if they aren’t there.

johanatan19:08:41

@shaun-mahood: did you add the :key to the v-box or to the h-box children?

shaun-mahood19:08:37

@johanatan: I added it to the parent v-box and it showed up in both the parent and children when I inspected them.

johanatan19:08:20

@shaun-mahood: that isn't exactly useful here though-- the h-box children are the ones that are dynamic.

johanatan19:08:46

@wasser: hmm, interesting. i too see the :key values in the React plugin. but not on the original data-reactid in regular inspector

johanatan19:08:07

@wasser: the worrying thing here is that all other keys I'm using elsewhere affect the data-reactid

wasser19:08:01

@johanatan: I attached the :key to each [h-box], inside the (for …).

johanatan19:08:27

@wasser: yep, me too 🙂

shaun-mahood19:08:32

@johanatan: Oh I misunderstood then - you want to add the :key to the children only then? I just tried that too and the result is that the child has data-reactid=".1.3.$my-key.$my-second-key" attached.

johanatan19:08:03

@shaun-mahood: wow, it seems that you're seeing something different than myself and @wasser

shaun-mahood19:08:52

I tried it on a single element though, so it may be something to do with how it works with a for in there

johanatan19:08:00

[i.e., that's the result I was hoping to see]

johanatan19:08:22

@shaun-mahood: well, it does need to be a loop of some sort since the list itself is of dynamic length

johanatan19:08:55

would it be too much trouble to try it in a loop as well?

shaun-mahood19:08:24

Yeah you will definitely need a loop for what you want to do, but it looks like it works for me outside a loop. Going to see if I can find an existing loop to test in my code and if not I'll throw a new one in to check.

johanatan19:08:23

[the other worrying thing here is that I see virtually no performance difference between the code w/ :key and that without]

shaun-mahood19:08:39

It would be interesting to get some more context around the performance - I've not done anything using re-com with a ton of elements.

johanatan19:08:38

Well, I see it take up to a second or so when adding or removing items after the child list has grown to probably a hundred

johanatan19:08:07

[which is probably way beyond the actual use case I'm supporting here-- but I was stress testing when I found these limits 🙂 ]

wasser19:08:37

@johanatan: and it seems to work fine, even without the react-id showing in the inspector (shrug)

johanatan19:08:27

@wasser: you wouldn't know if it is working or not unless you subsequently modified the list and saw performance improvements over not using :key

johanatan19:08:40

[so if you tie it up to on-click of a button which adds and removes items from the first 10% of the list and compare with and without :key, then we will know if it is working or not]

johanatan19:08:50

[also, you'd need to tweak how you are setting the IDs-- yours will always produce a range 0 -> length which would from React's perspective mean that things are only ever added or removed from the end (as React is using the ID as a proxy for identical?)]

shaun-mahood19:08:44

I can't seem to find any problems with data-reactid on my project, added a couple of loops and everything still seems to apply and work as I would expect (same as above without the loop).

wasser19:08:01

@johanatan: correct, I would typically base the :key off a unique property of the data element being rendered.

shaun-mahood19:08:20

@johanatan: It might be worth starting a test app from the re-frame template (with +re-com) and see if you can get something working from there - if you get one something up on github that I can download I would be happy to try it on my machine in case there is something else weird going on.

johanatan19:08:31

@shaun-mahood: ok, i will do that. thx!

johanatan19:08:17

there is one suspicious detail

johanatan19:08:45

The extra 'reagent18', 'reagent19' etc

johanatan19:08:15

My h-box are being generated in a function that has some subscriptions

johanatan19:08:38

And thus return a (fn [] ...) hence the extra component.

shaun-mahood19:08:47

@johanatan: There might be something useful here https://github.com/Day8/re-frame/wiki/Creating%20Reagent%20Components - it's my go to source any time I have any problems like that

johanatan19:08:38

Well, this isn't really a "problem" per se-- it's just another detail that perhaps differs from my example to yours and @wasser 's

johanatan19:08:08

Except, hmm, maybe I really want my :key to be on the outer component here and not the h-box

johanatan19:08:43

[but this still wouldn't explain why data-reactid on the h-boxen isn't correct]

johanatan19:08:54

May perhaps fix my perf issue though 🙂

shaun-mahood19:08:53

@johanatan: I would guess it's likely an issue with your component rather than the v-box - what happens if you pull it out and just call v-box directly with your existing code?

shaun-mahood19:08:59

I find that the vast majority of bugs I run into are in my code, so much that when the underlying library has a bug it takes me forever to realize it because I assume I must be doing something wrong. In reagent particularly nearly all my errors are in how I define and call components.

johanatan19:08:17

ya, i certainly doubt it is a bug in the framework logic

johanatan19:08:32

i am just seeking help to get it to work

johanatan19:08:59

so, any idea how to add a :key to Form-2 component?

johanatan19:08:58

[I tried putting the metadata before the (fn [] ...) returned from the inner function as well as before the call to the inner-function like so: [^{:key "blah"} (the-func)] ]

shaun-mahood19:08:13

Can you post the code that's not working? Also, try changing it to ^{:key "blah"} [the-func] and see if that works - I've got that in my code and I believe it worked for me and carried the key through.

johanatan19:08:50

that definitely won't work here: the-func is a func returning a func

johanatan19:08:57

^{:key "blah"} [(the-func)] rather

johanatan19:08:55

so the entire form is: (for [stuff collection] ^{:key "blah"} [(the-func stuff)] [and that form is assigned to :children of the v-box]

johanatan20:08:09

thanks a lot for your help!

shaun-mahood20:08:44

No problem! I'm surprised that [(the-func stuff)] and [the-func stuff] didn't, as I've got that pattern with form-2 components all over the place working successfully. Along with the form-1-2-3 stuff, the other thing I need pretty frequent refreshers on in reagent is when to call using ( ) and when to call using [ ]. Glad it's working for you - did performance improve at all?

johanatan20:08:09

No perceptible change in perf but I am doing one known sub-optimal thing in my own code so that is probably the reason.

johanatan20:08:26

How does [the-func stuff] pass arguments to the-func ?

johanatan20:08:34

Btw, suppose I have N of these vboxes with varying numbers of hbox children in each. Do all of the :keys across that entire structure need to be unique or is it ok if keys are only unique across one leaf branch?

johanatan20:08:14

oops, nm. I think I answered my own Q-- the parent key is represented as part of the child key so yes unique within one leaf branch is sufficient

johanatan20:08:27

[i.e., React concatenates/propagates them automatically]

johanatan20:08:00

@shaun-mahood: I'm familiar with all of that and follow it precisely

johanatan20:08:28

i.e., the-func takes some arguments & returns a closure over those (and some of its own introduced) arguments. pretty sure if you try [the-func a b c d] where a b c & d are said arguments, it will not work

johanatan20:08:03

whereas [(the-func a b c d)] will.

shaun-mahood21:08:21

@johanatan: Might be - I think it primarily depends on how you are defining the component and what it returns. Tough to get into the details of that part without seeing the code, I just know that everything I've done has been callable using [the-func a b c d] without a problem and I don't think I've seen the pattern [(the-func a b c d)] required anywhere else. Not sure that it actually causes any problems, though.

johanatan21:08:58

Hmm, interesting

shaun-mahood21:08:48

@johanatan: This is probably the point where our discussion gets less and less applicable to solving actual problems 🙂

johanatan21:08:16

Ya, I will try it your way and see what error i get

johanatan21:08:34

(but honestly I fail to see how it could work)

johanatan21:08:34

Oh, my apologies. It looks like it does as you claimed with square brackets.

johanatan21:08:52

Oooh, and the resulting output in the React plugin is nicer: now those components are named rather than anonymous "reagent-XX"

shaun-mahood21:08:53

Awesome, glad it improved things for you. I think there is some potential for minor performance improvements there as well, but I doubt it would have any real impact (more of the "it's doing very very slightly less work" improvement than anything substantial).

johanatan21:08:08

Oh my goodness. And the performance issue is now gone as well! 🙂

johanatan21:08:23

Actually no. It substantially improved performance

shaun-mahood21:08:26

Oh cool, you must have been running into one of the react-specific performance quirks. Glad it helped!

johanatan21:08:07

Ya, it doesn't seem like it should've affected performance.

johanatan22:08:42

Turns out the sub-optimal factor from my code [which I thought might have been the reason why :key didn't solve it] has virtually no impact at all on performance (up to 300 children and counting)

johanatan22:08:50

Basically the following function:

(defn next-idx [map]
  (+ 1 (or (apply max (keys map)) -1)))
is called when a child is added to determine its index.