Fork me on GitHub
#re-frame
<
2019-09-18
>
mikethompson00:09:14

@johanatan browsing back to previous epochs shouldn't "kick off event dispatches". Hmm. If I read between the lines, I'm guessing you mean this: 1. browsing back to an epoch and then 2. clicking buttons and causing new events (and new epochs) 3. but wanting to stay on that historical epoch, rather than moving to the latest? If so, there's no way to do that currently. It could be a feature we could add, I guess, but I'm puzzled about the usecase ... I would have always thought the latest epoch was always interesting and the one you needed to inspect

johanatan02:09:55

@mikethompsonno, actually i didn't mean that. basically in my on-render, i am dispatching a couple of events. so, if a render occurs, those events get fired.

johanatan02:09:20

i.e., there are literal rf/dispatches in my render logic.

johanatan02:09:45

the idea is that when the component first renders, it kicks off some ajax requests to load the data it requires in the bkg.

johanatan02:09:03

i'm not sure where any better place to kick those off would be ...

johanatan02:09:16

as even if i choose a "parent" component, i'd have the same problem.

mikethompson02:09:58

Right, I see. My advice would be to not do that. Let me get a couple of links ...

mikethompson02:09:06

That link has a further link to Eric's write up (PurelyFunctional)

mikethompson02:09:20

So the overall rationale is that we don't want our views to be in any way imperative. We don't want them effectful. We only want them rendering a view of app-db.

johanatan02:09:20

Yea, i get that and think it makes a lot of sense. I just wasn't seeing how to avoid it in this case. Looks like I will need to have a mapping from route -> loaders.

johanatan02:09:54

because the rendering is happening due to the user typing in a particular url (or due to an internal app redirect from another location)

johanatan02:09:30

not all routes will need data loaders but for the ones that do, i can just have a mapping.

mikethompson02:09:30

Without knowing too much about your setup ... the event handlers themselves are an ideal place to initiate "effects" (queries to backend)

johanatan02:09:16

take this for example:

(rf/reg-fx
   ::internal-redirect
   (fn-traced [path]
              (set! (.. js/window -location -href) (format "%s/#%s" (.. js/window -location -origin) path))))

johanatan02:09:40

^^ coupled with secretary means that i don't have to have hardcoded reframe events for each state transition i want to have happen

johanatan02:09:56

my secretary setup associates route -> state vector.

mikethompson02:09:06

Events should be viewed as "user intent" ("I want to view customers now" or "delete that thing"). The event handler knows how to make the "users intent" happen: (1) inititate the query (2) change the panel shown etc

johanatan02:09:50

yea but i already have a ton of events and i don't want to hardcode all of these. basically i've re-implemented angular's ui-router in clojurescript.

restenb10:09:21

can anybody link a working example of file upload with server middleware using re-frame's built-in client?

restenb10:09:01

i think it just wraps cljs-ajax so i'm trying to follow the file upload example here https://github.com/JulianBirch/cljs-ajax

restenb10:09:07

the request is correct but i'm getting the dreaded java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.Var error on return, must be some middleware conflicts

jahson10:09:26

Like I've said above — you need to check the order of middlewares

jahson10:09:01

Or you can share your middleware config here.

henrik11:09:04

Whats a sane way to deal with state external to the app-DB that depends selectively on state contained in the app-DB? For example, setting the current URL when a subscription changes. Driving it as a side-effect of dispatching works, but requires me to keep track of who/what is modifying the app state, which I don’t really care about, only that it has changed.

pcj13:09:02

So this is just me thinking (because we use kee-frame to handle routing in our application) but I would assume you can add-watch to the app-DB and call the subscription's fn on new-state and old-state to determine if the item has changed.

henrik14:09:14

Thanks, the thought struck me. That’s what I would have done in pure Reagent, but I’m not sure what’s considered state-of-art to handle non-rendering-related-yet-event-independent side-effects in Re-frame.

henrik14:09:14

It seems I can add-watch a subscription for the data in question. I wonder if this is preferable to watching the entire DB.

henrik14:09:45

Basically, I’d add it on :component-did-mount and remove it on :component-will-unmount.

isak14:09:35

^, or if you use (ra/run!), it returns an object that you can dispose with (ra/dispose!)

henrik14:09:44

@isak Can you elaborate? Is this related to the above track suggestion, or is this something else?

isak14:09:46

@U06B8J0AJ Here is an example, it is basically doing what you were talking about in the lifecycle methods: https://gist.github.com/isaksky/75277660572c079a97b71babe80b5667

isak15:09:41

(it is more complicated because it is also trying to solve another problem, but ignore that)

henrik15:09:01

@isak Interesting, are you using this to avoid running text area updates through the DB, but still having it sync/react to changes in the app DB?

henrik15:09:06

An interesting solution, thanks for the suggestion!

4
henrik15:09:10

I’ve run into that problem previously, before I learned not to lean too heavily on the app DB for precisely everything.

henrik15:09:20

Yet, when containing component state in a ratom, you end up passing around a reference to it in a manner incompatible with the affordances that re-frame provides, so I can see the value.

isak15:09:11

yea, I was motivated to try to solve that problem, since there were a lot of ways in my app to change the same piece of data

restenb11:09:44

i mean, i cut down my middleware to literally just wrap-json-response and wrap-params (both of which are absolutely necessary for the app at the moment)

restenb11:09:13

so my middleware is (-> routes wrap-session wrap-params wrap-json-response)

restenb11:09:00

and I only have this problem with the built-in re-frame client, if I go back to my old cljs-http code with the same middleware everything is fine

restenb11:09:46

i'm not sure if the re-frame client expects :format to be set for example, but the cljs-ajax examples didn't require that

jahson16:09:42

@restenb https://github.com/JulianBirch/cljs-ajax#formdata-support? According to readme you need to add wrap-multipart-params middleware.

eskemojoe00717:09:11

Quick question, from what I can tell there are 3 ways to get an event to have data from the app-db: 1) Inject the subscription per the FAQ (https://github.com/Day8/re-frame/blob/master/docs/FAQs/UseASubscriptionInAnEventHandler.md). This seems useful for computed level 3 subscriptions, but overkill for simple level 2 or very simple level 3 (like a couple of nested get statements). It also seems evil for not component subscribed data. 2) Let the component have the subscription and submit it as data to the effect handler. I see many examples of this floating around. 3) Just extract the useful value from the DB in the event handler. Not sure if I should do this one or not for simple info that is in the DB. I've seen no examples of this. So my question is for simple low level DB information in an event, is it evil to do 3?

isak20:09:14

I think even a lawful-good person would be ok with 3 🙂

mikethompson20:09:34

I agree with isak, 3 is the most natural way You want your event handlers to be computing the new "state of the world", given "the existing state of the world" and "the new event". So getting access to the "the existing state of the world" via access to app-db, is pretty natural.

mikethompson20:09:45

2 is used rarely IMO.

henrik20:09:03

Chaotic good or at least neutral good, perhaps. The downside is, now your event handler has to know the location of the value in addition to the subscription. In my experience, it leads to a minor explosion of PlOP in a big enough app.

mikethompson20:09:48

That concern doesn't bother me

mikethompson20:09:22

I'm perfectly happy with subscriptions knowing about the structure of app-db and ALSO event handlers knowing.

henrik20:09:34

Fair enough, it’s bothered me a couple of times, and then it was really bothersome.

henrik20:09:33

I.e, hunting down all the cases where an assumption was made about the location of a value in order to nest it one level deeper or something like that.

isak20:09:34

@U06B8J0AJ One thing a lawful-good person could do in such cases would be to use a function to get the value in the handler, which could also be used other places as appropriate

mikethompson20:09:52

subscriptions are there to: 1. make sure that views don't know about the structure of app-db 2. perform that calculation of materialised views, as necessary

henrik20:09:58

@isak That’s the solution I ended up with in practice.

4
mikethompson20:09:31

event handlers have to mutate the structure in app-db so i can't see how they could or should be shielded from the structure of app-db

4
mikethompson20:09:28

slightly related: there is an interceptor called path which can "narrow the focus" of an event handler.

mikethompson20:09:41

In the same way that update-in narrows focus to a certain part of a map.

henrik20:09:13

@mikethompson It’s just that when two things make an assumption about a location, you have to keep them updated in lockstep if you need to refactor that location for any reason (for example, a value became more complex and went from being a string to being a map; now the desired string is a level deeper than before). In addition, this seemed unnecessary as well, seeing as I already had a named reference to the location of that value: the subscription. If something is of such domain importance to me that I have a named reference, I want to use that name everywhere, since it is the semantically meaningful unit. The physical location is unimportant.

mikethompson20:09:12

We find that our event handlers tend to read/write fairly "locally". Ie. almost all the data they need to read is proximal to the data they they will be mutating. And given they have to know the structure of what they are mutating its not much of a problem for them to also know about the nearby, related data too. Perhaps a bit depends on how you design your app-db? Not sure.

mikethompson21:09:54

Yeah, that must be it. The correctness of the solution will depend on how widely your event handlers have to read when doing their job. I believe it is generally a good idea for them to read/mutate in a narrow area (although obviously that might be harder or easier depending on the domain)

henrik21:09:34

Yeah, maybe it’s a design question. I have a fairly complex, deeply nested tree with about a dozen events operating across it (inserting, deleting, swapping values, clearing, removing sub trees, various kinds of reformatting and updates, and so on) in addition to interceptors (selectively reformatting and persisting the bare minimum serialized in the URL, storing the rest in cookies/DB, querying external services) etc., and subscriptions to match. I’m not sure if this counts as a complex component or not, but it’s seemed complex enough to me in that chasing location assumptions has been tedious enough for me to want to do something about it. I’ve been trying to pull the entire thing more into the realm of the semantically meaningful rather than the incidental aspects of the particular representation as laid out in the DB (though that connection must exist somewhere of course)

eskemojoe00714:09:30

Thank you guys! I had to step away last night. Interesting discussion. I'll use #3 with impunity then! My uses case is the app-db has an auth-token that needs to be used to make http requests.

eskemojoe00714:09:28

One final question about 3 is the async nature of things. I have a event that gets the key from local-storage, and has a effect map like:

{:db (-> db
            (assoc :auth local-store-auth)
            (assoc-in [:auth :auth?] false))
    :dispatch-n (list
                 [:auth/check-token (:auth-token local-store-auth)])}
You can see I'm not doing 3 in the :auth/check-token event and explicitly passing it. I could skip that, but I worry that on occiasion that could get fired before the :db part updates the app-db. Does that make sense?

isak20:09:25

Somewhat related to the question above, what do people think about making it so stuff like @(subscribe [:something]) could be parameterized with the db? (Even if the sub data is not literally stored in app-db, it seems like it would be logical). Note: This is to make it easier to get sub-data in an event-handler.

mikethompson21:09:15

@isak I'm not sure what you mean by "could be parameterized with the db"

isak21:09:18

@mikethompson For example having another arity version of rf/subscribe (allowing to pass in the db, instead of implicitly using the global one), which would then make sure the sub-data was up to date with that version of the db, and also didn't cause leaks in event handlers.

mikethompson21:09:43

@isak That approach seems to bump into a lot of the core design decisions in re-frame. Might be difficult.

isak21:09:54

Ah, I see