Fork me on GitHub
#re-frame
<
2016-09-29
>
samueldev00:09:14

pretty straightforward handler...

samueldev00:09:18

(reg-event-db
  :select-country
  (fn [db [_ country-id]]
    (update-in db [:selections :selected-country] country-id)))

samueldev00:09:24

db is as I would expect, my app db

samueldev00:09:37

and country is as I would expect; an integer (in this case 2)

samueldev00:09:19

Uncaught TypeError: f.call is not a function

samueldev00:09:07

(potentially worth noting? that I’m not using any interceptors and am sticking with the old pattern of every handler having access to the entire db, no contexts)

superstructor00:09:54

I don’t see an issue with that code, are you sure the exception is from that handler @samueldev ?

danielcompton00:09:52

@samueldev do you mean to use assoc-in?

superstructor00:09:16

ah good point @danielcompton, didn’t spot that either! 😉

danielcompton00:09:33

I’ve written that bug more than once 🙂

samueldev00:09:28

classic clojure fail

mattly00:09:38

I really wish it were set-in

mattly00:09:16

though I suppose at that point you get into naming clashes

superstructor00:09:21

I’m interested to know since you’ve mentioned you have a significant re-frame app @danielcompton, how do you deal with db schema / state for shared components ? e.g. say for example you have a password component that is used in more than one form that has some of its own subs and handlers, does it write to its own db space, or do you pass in a base db key sequence for the shared component to conj onto the key sequence for all its db operations ?

danielcompton00:09:52

don’t quite understand, can you reframe that question?

superstructor00:09:45

@danielcompton at the moment we have stuff like:

(defn emails
  [{:keys [title db-base-ks]}]
  (let-subs [pending-emails         [:email-list/pending-emails db-base-ks]
 …
where emails is a shared component for a list of emails between different types of forms, and db-base-ks is passed into subs/handlers to be used like:
(reg-sub
  :email-list/pending-emails
  (fn [db [_ db-base-ks]]
    (get-in db (conj db-base-ks :pending-emails))))

samueldev00:09:48

I hope that’s the first time you’ve used that @danielcompton

danielcompton00:09:27

ah, I think I see what you mean, do we parameterise functions with a key sequence?

danielcompton00:09:02

We would probably put that shared thing under it’s own key

danielcompton00:09:29

but our apps are quite panel like, where each panel mainly operates on it’s own data and there isn’t a lot of sharing, so that may not work for you

superstructor00:09:35

Yeah we have shared things of which there could be multiple instances on the same panel/form so it must have a way to seperate the state between the usages of the common component, make sense ?

superstructor00:09:57

Not sure if passing in a keq sequence as a db base location is the right solution yet, but it is what we are doing at the moment anyway.

superstructor00:09:49

Do you have any shared functions between different subs or handlers ? @danielcompton

danielcompton00:09:20

oh, I think I see what you’re saying. You’re looking to create common view components which source their data from different places in app-db?

superstructor00:09:34

yes :thumbsup:

superstructor00:09:54

that is the first question, the second question is just about if you have any functions shared between subs/handlers ?

danielcompton01:09:30

probably? sorry, I’m feeling a bit dense today, don’t think I’m totally getting your main point

johanatan01:09:02

@superstructor I have re-used components and I am using the "pass in a base db key sequence" approach

johanatan01:09:29

I've also found reagent/cursor to be quite helpful in that implementation

johanatan01:09:40

I've also almost completely eschewed use of subscriptions in favor of localized reagent/atom or reagent/cursor (although I can see that it would be nice to have a more frequently utilized global event stream I could tap into for debugging purposes).

johanatan01:09:54

When components are cordoned off this way, however, debugging is also localized to individual components.

johanatan01:09:38

So, if I were going to answer your second question it would be "no" because I have very few subs/handlers to begin with.

superstructor01:09:10

thanks thats very interesting @johanatan, we probably don’t want to introduce localized reagent/atom or reagent/cursor as we’d lose some of the benefits of re-frame sub/handler model.

superstructor01:09:02

all good @danielcompton, in other words do you have a utils.cljs or helpers.cljs for shared operations on db state between subs and handlers ?

johanatan01:09:45

@superstructor yes, but in my view the sub/handler model should be reserved for very high level concepts that the entire app cares about; not such things as whether a txt field has this string or that string or whether a radio button is checked or not.

johanatan01:09:23

and you can still send those "globalized concerns" as summary events when a transition is taking place from one section to the next

johanatan01:09:06

the diff being pre-processed v raw

superstructor01:09:05

what benefits/disadvantages have you found with that approach ? @johanatan

johanatan01:09:42

less boilerplate

johanatan01:09:53

most handlers are one liners and they just set something in app-db

johanatan01:09:00

you can instead just do that one line in place

johanatan01:09:10

rather than creating a new subscription and handler to do the one line 🙂

johanatan01:09:52

i do have some handlers which are paragraph-ish in size and for that it definitely makes sense

johanatan01:09:00

but a ton of my events are one liners

superstructor01:09:13

thanks interesting, so by localized you mean that the reagent/atom or reagent/cursor is somewhere in your view form 2 or form 3 components right ? @johanatan

johanatan01:09:18

i would imagine another advantage is if you were going to sniff out the global event stream, you wouldn't be bogged down with mundane details but rather would see only high-level/important/summary level events in your debug stream

johanatan01:09:42

[but that can cut both ways obviously-- would be nice to have everything in the stream (a la Kafka) and merely filter as needed]

shaun-mahood01:09:03

@superstructor: Alternatively, I've found that I have never needed or wanted either r/atom or r/cursor - it's very much possible and recommended by the re-frame "philosophy" to keep all your app-state in app-db. I have absolutely used regular atoms as part of reagent components, similar to how it is done in re-com, and that works well for the apps I've worked on. Experiment and see what works best for you!

superstructor01:09:32

do you test that localised state @johanatan ? It strikes me that the testing storey would be a lot harder than in re-frame subs/handlers ?

superstructor01:09:49

I agree in principal, we just have some challenges to solve in practice that we havn’t worked out yet. At the moment, we almost religiously keep all state in app-db and do all state changes (not just app-db) via reg-event-db, reg-event-fx, reg-cofx, reg-fx etc. Of course with this we’re now having the problem of how to manage shared things based on app db (like common subs/handlers) and how to share locations within the app db between subs/handlers etc. @shaun-mahood

samueldev01:09:39

Is it a bad idea to send arbitrary (dispatch)’s from inside an interceptor?

samueldev01:09:35

Trying to figure out how best to structure my app… in the simplest possible explanation; it’s a ostensibly a hierarchical database view… easiest way to explain it is to think of it like a parent -> child -> baby setup where I have a :location section in my app-db to determine where someone is currently navigated to in the hierarchy

samueldev01:09:33

if someone is viewing a child and they navigate to a parent that is not the parent of their currently selected child (saved in :location in app-db), I need to clear their child selection & transitively every other selection they might have down the hierarchy

samueldev02:09:23

I can think of a way to do that off the top of my head via interceptors and dispatching a wipe-everything-below-this-in-the-selection-hierarchy event, upon receiving a “they chose a new parent” event

samueldev02:09:29

but I feel like that might be gross

johanatan02:09:16

@superstructor: good question. In any app I think there is a thin veneer of functionality that is beyond the utility of automated testing (such as hover/accent colors and such). I'm doing automated testing just below that level at the actual business/conceptual logic (as I'm really not interested in using Selenium or such to simulate clicks and the like).

johanatan02:09:28

Regarding the "re-frame way": the way I see it is it is nice to have the sub/handler mechanisms in place for when you need/want them but I'm no fan of blind rote and I think a more balanced hybrid approach is attractive. As Shaun mentioned, if you are using / consuming components from other sources, your app is already doing this.

johanatan02:09:21

@superstructor: and actually now that you mention it: perhaps "is it worth testing?" is a good proxy for "is this worth making a subscription & handler for?" and/or "is this worth putting in app-db?"

johanatan02:09:14

i.e., choosing which is veneer vs which is conceptually important to the business

mikethompson02:09:24

@samueldev I don't see anything wrong with the wipe-everything-below-this approach

mikethompson02:09:25

In response to an event like :parent-change ... it seems to make perfect sense for the handler to modify app state in various ways. Cleaning up the old parent, preparing for the new parent.

mikethompson02:09:28

@johanatan stepping outside the architecture is, of course, absolutely possible. But as Rich Hickey says "Programmers know the benefits of everything and the tradeoffs of nothing" . There will be tradeoffs to choosing to break the fundamental architecture.

mikethompson02:09:25

We find that sticking with a certain discipline around state very useful. That discipline is reframean in nature. But each to their own.

johanatan02:09:28

I think I have a fair grip on the trade offs here and they seem quite reasonable/livable

johanatan02:09:42

If a value is only ever read by one place, why would it need to be global?

samueldev02:09:59

thanks for your input @mikethompson I appreciate it

johanatan02:09:14

Or to put it another way: how many actors/agents/controls/subsystems/components/entities care if one of my buttons is currently hovered or not?

johanatan02:09:22

This type of separation of concerns/encapsulation has served many GUI systems well over the years

shaun-mahood02:09:24

@superstructor: For the case of common view with 2 different data sources, I've solved it before by refactoring the re-frame control logic up a level and so that I'm passing the problematic data in as an argument to a basic reagent component - so in your example, the emails component would have pending-emails passed in by the parent. It can certainly introduce other issues, but in my apps I've found that every time I want to use that pattern it's a sign that I've made a wrong decision somewhere (which doesn't mean you have, as there are tradeoffs for doing it that way that may not work well for what you need).

shaun-mahood02:09:46

@johanatan: I usually look at it as a question of whether I could spin a portion off as it's own common component, or if it only makes sense in the specific app - things like button hovering certainly make sense as a concern that a common button component could deal with on their own.

superstructor03:09:12

why is it that reg-sub doesn’t provide something similar to interceptors ala reg-event-db/reg-event-fx; e.g. being able to use something similar to re-frame.std-interceptors/path for subs ? or being able to change query-v.

mikethompson03:09:32

The equivalent of path is to use another subscription

mikethompson03:09:04

(reg-sub  
    :something 
    :<-  [:other]           ;; this is the equivalent of path 
    (fn [other query-v]     ;; first param supplied by [:other] subscription 
         ....))

mikethompson03:09:32

But no alternative to changing query-v

superstructor03:09:09

good point, path was a poor example, maybe there are not enough real use cases for it to be important.

samueldev04:09:41

so apparently 2 != 2

samueldev04:09:47

(.log js/console "are they equal?" (= selected-climb-id (:id %)) selected-climb-id (:id %))

samueldev04:09:15

head scratch

mikethompson04:09:28

@samueldev one of them will be a string and one an integer

mikethompson04:09:39

They print the same

samueldev04:09:00

that’s the realization I came to as soon as I typed it, yeah. 😞

samueldev04:09:40

I used re-select recently which touts that it’s inspired by re-frame

samueldev04:09:59

is it so in terms of composing selectors (`subscriptions`, in this case)?

samueldev04:09:54

I’m finding myself writing duplicate filter logic all over the place (the filter logic for "find X by ID" is needed in the plain ol' “give me X by ID” sub, as well as in other subs that are derivatives of and/or are compositions of multiple “find X by ID” subs)

johanatan05:09:15

@shaun-mahood: yes, that's precisely the guiding principle I'm using as well. Of course sometimes things don't end up being as isolated as one first expects (or vice versa) and adjustments need to be made but these types of program transformations are super easy in Lisps anyway so I don't really mind it

shaun-mahood05:09:40

@johanatan: yeah, the flexibility is fantastic. I've found that in general it's way more possible to fit things around the problem than what I used before clojure.

johanatan08:09:57

@shaun-mahood out of curiosity, what have you used before clojure?

shaun-mahood13:09:25

@johanatan: Primarily C# and JavaScript for more substantial work

andre14:09:49

new version is comming

andre14:09:49

html debugger window is fully generated, you don't need any configurations, just re-frisk lib

andre14:09:14

one thing, i pass the atom in the debugger window, it dereferred fine, but reagent doesn't update it, i decided to pass it in the window using reagent function - next-tick, but it calls only once, what do you think, what should i use to frequently pass this data? on the demo i'm using setInterval, but it's not good

richiardiandrea15:09:04

@andre good job! Will this be part of data-frisk or standalone? I use cljs-devtools at the moment for this use case

oliy21:09:41

enjoying re-frame 0.8.0 - all the side effect stuff. thanks, it solves a lot of the things i was uneasy about