hi, if I want some impure data that is parametrized at runtime, like a function, should I create an interceptor that precalculates it using the db and the event vector? examples: query dom element for its bounding rectangle, or create an arbitrary number of UUIDs. injecting cofx will not work because I have no access to event vector there.
so far I have been cheating - I inject the whole function that queries DOM into cofx map, to be called inside the event handler. but the only thing that I achieve here is better visibility of the impure input that I use, I can very well just call the same function inside event handler using its global name, not much different.
it is actually not easy to precompute DOM values. I have a logic that calls .scroll on something first, and then asks for bounding rectangle.....
perhaps my current workaround is acceptable?
Another strategy is to add a window onscroll handler that queries DOM for the bounding rectangle and dispatches a re-frame event to put the dimensions in the db for your subscriptions and event handlers to use. If onscroll events can come from the user as well as your .scroll calls then you'll want to debounce it.
Zed, it would be a lot of queries and it will degrade performance almost certainly. a PDF can easily have > 700 pages.
I render them lazily, but placeholder rectangles are rendered eagerly
I just discovered :fx grouping effect to execute things sequentially. I am using it liberally now
(this is not related to precomputing bounding rectangles)
another question please. if I want to set timeout executing a function, and save timeout id to the database so it can be cancelled, I guess I need to: 1. reg-event-fx that will take a function to be executed in the event vector, and return set-timeout-and-save-to-database fx...?
Maybe you'll be interested by dispatch-later
https://day8.github.io/re-frame/api-builtin-effects/
heh. I definitely need to read this tutorial again, now as I started tinkering with the code.
but was not this chapter about computing something based on db data?
or reg-fx set-timeout that sets the timeout and then sends an event with timeout-id to get it saved to the database on the next tick?
basically the problem is that setting timeout and saving timeout-id to the database must be chained, because only after the first fx executes, data for the second fx becomes known
trivial to do it in imperative way, hard to do (for me) by generating descriptions of actions instead of actioning them on the spot...
like this?
(rf/reg-event-db set-timeout (fn-traced [[db db-path function timeout-ms]] (let [timeout-id (js/setTimeout function timeout-ms)] (assoc-in db db-path timeout-id))))
sorry should be reg-fx not reg-event-db
The timeout id doesn't have to be saved in the db. You could use an atom, as is done here: https://github.com/district0x/re-frame-window-fx/blob/master/src/district0x/re_frame/window_fx.cljs
but it won't hurt to have it in the db, will it?
also, is there a canonical list of re-frame contribs (like the above re-frame-window-fx, or re-com) that I should know of?
You've got a side effect in your reg-event-db handler, setTimeout. The advantage to window-fx's storing timer id's outside the db is that reg-fx handlers can do that without needing the db. There's a list of re-frame libraries in the re-frame documentation.
I changed reg-event-db to reg-fx already, so not an issue
well, re-frame-window-fx is not mentioned in re-frame documentation https://github.com/search?q=repo%3Aday8%2Fre-frame%20re-frame-window-fx&type=code
got this error now ' ":fx" effect should not contain a :db effect'. I am tempted to write an effect :sequential that will allow multiple :db in them or even passing value from one effect to the next, similarly to -> macro
I wanted to action fx on DOM first, then save updated state to the database
Hi. When continuing an existing conversation, could you please use threads?
hi, sure. sorry, it is indeed spamming the main thread.
I am accustomed to irc
I will think first if I can factor out all db changes into one group.
so, to factor out all database changes from multiple functions into one, I guess I need to return maps like {:db #(update-in % [:rendered-page-strip pdf-id] dissoc :will-be-proposed-reference-page-number)}
then I can chain the values of :db keys by threading the real database through it.
I guess the solution in re-frame spirit would be to dispatch individual events to trigger all database modifications one by one, rather than "merge" them from multiple function return values.
a hard one as I have many sequences of actions like, 1st cancel existing timeout, then create a new one.
2 steps. but I have longer sequences.
> I guess the solution in re-frame spirit would be to dispatch individual events to trigger all database modifications one by one, rather than "merge" them from multiple function return values. I'd say on the contrary. Events convey user intent (or, well, the intent of some JS handler). There of course can be "util" events but they might be hard to reason about and can get quite hairy.
okay. so my event is "pages container scrolled or pages container component-did-update" - and it triggers a lot of checks, so you would lump them all together? in my imperative code though it is like this: IF a page above the current one has changed height (e.g. because it got lazily rendered), THEN (1) scroll up by page height delta to compensate for it (so the user can still see the current page in the same position), AND THEN update some page coordinates from DOM in atom.
so a sequence: action first (scroll), then impure data (read DOM), then save it
> pages container scrolled This is a good fit for a re-frame event. > pages container component-did-update But this is not. An update is caused by some other action - ideally, it's that action that should trigger relevant re-frame events. But of course, that's not always feasible. > IF a page above the current one has changed height (e.g. because it got lazily rendered) A good fit for a resize observer, which by itself is also a good fit for a re-frame event since resizing can be caused by both a user action and some JS code or even, as you said, lazy loading. > THEN (1) scroll up by page height delta to compensate for it (so the user can still see the current page in the same position) Alright, a good fit for an effect. > AND THEN update some page coordinates from DOM in atom. This part I don't get. What atom? Something beside re-frame? If you consider those coordinates, whatever they are, a part of the app's state, then you should store them in the re-frame's app-db. If the coordinates are not a part of the app's state, you don't have to manage that data via re-frame at all. Plain Reagent machinery would suffice.
hi, sorry I missed that. thanks for your remarks. as of the last point, I still did not solve it. I would like to have an effect that scrolls the PDF pages container to compensate for lazy page rendering above the viewport, and then (after the scroll) update the app-db from some DOM coordinates AFTER the scroll. the example effects I saw do not modify the database. in imperative style I would do this
(rf/reg-fx scroll-dom-element-and-save-dom-stuff (fn [[element left top]] (.scroll element left top) (swap! re-frame.db/app-db assoc-in [:path1 :path2] something-from-dom)))
You can achieve the same result while staying in the realm of re-frame recommendations by leaving only (.scroll ...) in the effect handler and moving app-db modifications to the event level.
the db effect is executed first, so no good. I need to change DOM first (e.g. scroll) and only then I can read something from DOM state and put in the db
I can dispatch an event after scrolling but I feel uneasy about it, as reading the dom and saving the result will happen in unspecified moment after my action, so something else may happen in between
Why is the order important here? Changing app-db does not immediately result in a re-render. The re-render will be scheduled for the next frame anyway.
the delay may not be as important as I think; so would you dispatch after scroll from the fx?
That, or change the db in the corresponding event handler since it shouldn't affect things at all.
in the corresponding event handler I have no info yet, because the scrolling did not happen. and I want to scroll and THEN read dom coordinates.
I see. And while it's possible to compute them yourself, you'd also have to consider the edges.
Then yeah, just dispatch in the effect handler. There's also dispatch-sync if you worry that a potential delay might cause any issues.
thanks. dispatch-sync will probably not work, because it checks if I am not in handling event context already
so I will stick to dispatch and start worrying if the delay is important after all, think may be ok
what is the rationale against registering a pure function with the right parameters defined already in the toplevel as an event handler? if I do it I will be able to call this function from REPL. I see people tend to write body with logic into reg-event-db|fx but then it seems I cannot easily call this function from REPL.
for example I defined event handler with body spelled out
rf/reg-event-db copy-proposed-reference-page-number-to-timed-out (fn-traced [db [_ pdf-id]] ... )
and then I tried to call it from REPL using a complex accessor
((:before (last (re-frame.registrar/get-handler :event :buboflash.pdf-page-strip/copy-proposed-reference-page-number-to-timed-out))) ....args...)
not only it is complex but it seems that it is already wrapped and it is not directly the function that I defined there.
have you got a habit of simply writing (rf/reg-event-db copy-proposed-reference-page-number-to-timed-out my-function-defined-elsewhere) ?
There's nothing against it. It's just that if you don't use the handler anywhere else, including REPL, it makes little sense to define it separately. Personally, I almost never use a CLJS REPL.
I see. I use REPL all the time. I will define functions separately to make them callable from REPL then. Thanks!
I agree with @p-himik in that I don’t often use the CLJS REPL. But when I do use it, it’s usually something like this:
(comment
(-> @re-frame.db/app-db (get-in [:path :to :interesting :data])))
I’m perusing my comment blocks in my cljs files and I found this other one:
(comment
(rf/dispatch [::debug-remove-auth-token]))
I guess I chose to write that instead of creating a button or something to trigger the event.
Most of my comments are like the previous one where I’m “looking inside” app-db to check some value. The rest are simple one-and-done calls to test utility functions.I have used Common Lisp a lot so I got accustomed to REPL
Live reload > REPL for a lot of CLJS work. (UI, at least.)
But the symbol thing isn't a bad idea. I think the official reason not to use a symbol is that keywords are something that resolve to themselves, which isn't the case for symbols, and the former concept maps more closely to how re-frame works.
I think if you could register callbacks for when var definitions changed in Cljs, re-frame might have done it differently.
I still use keywords for event names
but I do not use anonymous functions as handlers
I started doing this
(rf/reg-event-fx set-reference-page-from-want-if-near-top [(rf/inject-cofx pdf-view-model/get-page-bounding-client-rect-wrt-scroll-container)] set-reference-page-from-want-if-near-top)
so set-referene-page-from-want-if-near-top is a function defined above the registration form
Right yea. Sometimes people make a macro that does this, so the keyword isn't even in the code (because it isn't really needed.)
I did not figure out yet how to do macros in cljs. I can see they have to be defined in clj.
Here is a good example: https://code.thheller.com/blog/shadow-cljs/2019/10/12/clojurescript-macros.html
anyway, migrating to re-frame is challenging for me. I have sequence of actions in my imperative code, like scroll an element, read DOM data, save to an atop.
atom
on one hand re-frame wants me to capture it all with a single event / user intent, on the other hand the sequence is not possible to express directly, so people execute one step, then send an event with result to trigger the next step
but it is incredibly rigid coding style. only fit for ONE purpose.
it feels a lot like (step1 (fn [r1] (step2 r1 (fn [r2] (step3 r2....)
where r1 is result 1 etc.
brb, I may be misunderstanding. perhaps it is possible to refactor the code into a better shape