Fork me on GitHub
#re-frame
<
2018-03-22
>
witek08:03:56

I understand how to load data from server using reg-event-fx. But then my view components need to dispatch this event on load. Is there a pattern how to trigger AJAX when a subscription is dereferenced? Or should my view components dispatch events when rendering and a subscription returns nil?

curlyfry09:03:56

@witek Why do the view components need to dispatch the event? I find it a lot nicer to trigger events that load data from the server when a user navigates to a certain page, or clicks a button etc

curlyfry09:03:44

e.g (using secretary in this example):

(defn app-routes []
  (defroute "/" []
    (dispatch [:page-entered :main]))) ;; Where :page-entered for example sets the current page to :main and loads appropriate data from the server

mikethompson09:03:00

@witek "load on mount" is not idiomatic to re-frame

joelsanchez10:03:46

as an example of "loading things on page change" instead of on component load...

(routes/define-route!
  :frontend.product
  {:name ::page
   :url ["product/" :id]
   :component page
   :init-fx [::init]})
::init being:
(rf/reg-event-fx
 ::init
 (fn [{:keys [db]} _]
   {:db (assoc db state-key {:quantity 1})
    :dispatch [::fetch (routes/ref-from-param :id)]}))

witek10:03:03

@curlyfry Your solution requires the event handler to know what view components are on the page. This seams very problematic to me. Especialy when using generic cross cutting view components which are plugged in to nearly all pages. Or when these are enabled/disabled from the main menu. Then the event handler needs to implement all this logic too.

joelsanchez10:03:28

I have that problem too and I used to load things on component init (form-2 components) but it's definitely better to either: - pass whatever data it needs in the props - have that data in the db, and pass a db path or subscription - prepare the given component on app or page load (define a dependency of page on component and dispatch whatever is needed on page load) - define an init-fx for that component and require implementors to dispatch it before using the component (this being the worst option)

curlyfry10:03:05

Yeah, I prefer to keep my components as "dumb" as possible

curlyfry10:03:50

They just render the "truth" that's in the app-db

witek10:03:07

This way it seams very difficult to implement reusable view components which are independent. I had view components in mind which are just referenced. And the components then themselves subscribe required data. The user does not have to know all these internals of the component.

curlyfry10:03:46

I personally find it easier to reuse components when they have less logic

witek10:03:17

But then you have to repeat all the missing logic in all event handlers

curlyfry10:03:20

I find the app much easier to reason about when all loading of data, view switching etc are event driven

curlyfry10:03:28

If you have a :show-my-component event, that event should probably load the required data (if needed)

joelsanchez10:03:37

and as re-frame devs always say: events should represent user intent and should be dispatched as the result of user's actions, those being: entering the app, entering a page, clicking a button, ....

đź‘Ť 4
curlyfry10:03:44

That way the logic becomes relatively reusable

witek10:03:26

So if I have a component which displays a page-view-count with data from the server. And this component is visible on 9 of 10 pages. I should implement the data-loading in 9 event handlers? And if the visibility of this component depends on some url-parameter, this if is also repeated 9 times? Seams odd to me.

witek10:03:24

and when the component changes and needs more data, then I have change almost all my event handers? Just because one view component changed?

mikethompson10:03:52

@witek re-frame encourages you to think this way: 1. The views are simply derived data (derived from the current application state) 2. Views do not "cause things to happen". They are not imperative. 3. On the other hand, events come with mutative intent. 4. Events capture a user's intention. I know this is a different way of thinking, but I promise you that once you get used to it, it works better than making views "agents of change" :-)

witek10:03:57

I get the "easier to reason about" argument though

mikethompson10:03:31

So, all "changes" should arise out from events.

mikethompson10:03:51

In fact, the ONLY reason views changed was because of an event

joelsanchez10:03:54

you don't need to change "all your event handlers" because the event handler for loading whatever data that component needs is just one event, you dispatch it in the pages and then it does whatever

mikethompson10:03:01

So within the event handler which is causing the state change, which reflects in a UI change, you make THAT the place for triggering the "whatever is necessary for the view"

joelsanchez10:03:23

I would: - make the component pure - define an event for loading the count (::load-count) - define a ::load-url-params-data event, which would parse the URL params and decide to dispatch some events, ::load-count possibly one of them - dispatch ::load-url-params-data when a relevant page is accessed

joelsanchez10:03:48

no duplication and no need to change things everywhere

witek10:03:47

The duplication is in "- dispatch ::lad-url-params-data" on all relevant pages. But ok, I get it.

joelsanchez10:03:34

you can also define a set of pages that should implement that, or some fancier mechanism, but this is just a regular programming problem

joelsanchez10:03:03

or implement a global "route-changed" handler and decide if you should dispatch ::load-url-params-data or not based on whatever 🙂

curlyfry11:03:11

Agreed, should be possible to avoid that duplication

mikerod13:03:11

What if loading the data necessary for a component should be done at a more granular level than a whole page load

mikerod13:03:27

eg what if you want it to be more lazy than that. It just seems limiting to me that routing to a new page would have to be where all data “bootstrap data” loading is started from. Maybe I’m wrong though.

mikerod13:03:28

I guess other loading could happen beyond that, just by some explicitly triggered event. Maybe that is fine. I’ll have to experiment. I think i have a few “bad” components that try to load their own data if needed on a render. Fail.

pauld13:03:54

Maybe just start a bunch of timers on page load that trigger events more granularly?

pauld13:03:08

Maybe some data can be triggered / loaded via scrolling or mouseover events.

joelsanchez14:03:35

dispatching events when X component is in the viewport is (imo) doable and reasonable (although not very common), but loading data on component init doesn't help with lazy loading anyway

curlyfry15:03:34

@mikerod There's no reason to only do it on page load

curlyfry15:03:40

The events handlers that show/hide your more granular components could also fetch the data they need, for example

mikerod15:03:28

Yeah I think that makes sense.

mikerod15:03:07

I think I just wasn’t thinking it through enough a few times when I ended up putting the init/load event dispatch directly in a component render/mount

mikerod15:03:49

It seems reasonable to think there are Ui events happening that could be used to attach dispatch calls too

mikerod15:03:59

At more granular levels when needed

nenadalm18:03:27

I also load all data via events. But https://github.com/Day8/re-frame/blob/master/docs/Subscribing-To-External-Data.md seems to be what @witek is looking for probably?

dfcarpenter20:03:35

any larger open-source examples of a re-frame app other than the real-world/conduit app one?

mikethompson21:03:19

See External-Resources in /docs

mikethompson21:03:35

For a list of apps out there