Fork me on GitHub
#re-frame
<
2021-05-10
>
bastilla13:05:19

Hi. Does anyone happen to know: Under what circumstances does :on-mouse-over #(rf/dispatch [::events/change-deep-in-app-db]) trigger all other components on a page to re-render, even though those other components subscribe to different locations in app-db? This is what I am struggling with right now. I tried to reproduce this with a tiny fresh project to spread to you, but I can't reproduce it. Due to this phenomenon now users would watch a movie in a page, move the mouse over some other areas and the video stops playing and resets. 😞 Is there a typical mistake / scenario where this behaviour gets triggered? (Sorry for being unable to provide snippets! But it's actually pretty straight forward. It's all @(rf/subscribe [::subs/deep-but-eventually-disjunct-area-in-app-db]) and events do likewise.)

p-himik14:05:11

It may be the fact that you're using #(...) within the view function. Each call to the very same #(...) returns a new object.

p-himik14:05:00

By "the view function" I mean the view that gets re-rendered - not the one that dispatches the event.

bastilla14:05:42

Yes, I am using:

[:img {:src src
           :on-mouse-over #(rf/dispatch [::events/swap-logo :highlighted])
           :on-mouse-out #(rf/dispatch [::events/swap-logo :normal])}]
which triggers other (disjunct) areas on that page to re-render. But then how might this be incorrect? I mean, how to circumvent? (Sorry, I don't want you (or anyone else) to solve my problems. But maybe a thought can already bring me out of my dead end here.)

p-himik14:05:35

You can use a form-2 component or reagent.core/with-let for that. But to reiterate - that :video and other areas on the page should be changed, not this :img.

p-himik14:05:03

Also, IIRC re-frame-10x has some built-in tracing that might give you an idea what exactly causes a re-render.

bastilla14:05:58

Okay, Merci! I'll have a look into with-let and will rewrite into a form-2. Thanks @U2FRKM4TW !!!

👍 3
bastilla15:05:14

As a final post. All this jazz doesn't fly, but I admittedly do have a somewhat dynamic/complex web view. Alas the triggering snippet (now in form-2)...

(defn logo []
  (let [logo (rf/subscribe [::subs/logo])]
    (fn []
      (let [logo @logo
            src (case logo
                :normal "/img/logo/logo.jpg"
                :highlighted "/img/logo/logo2_1.png"
                "/img/logo/logo.jpg")]
        [:img {
               :src src
               :on-mouse-over #(rf/dispatch [::events/swap-logo :highlighted])
               :on-mouse-out #(rf/dispatch [::events/swap-logo :normal])}]))))
...does still trigger a -re-render to the totally passive video component which is in form-2 as well now:
(defn common-sub-page []
  (reagent/with-let [current-route (re-frame/subscribe [::subs/current-route])]
    (fn []
      (when-let [current-route @current-route]
        (let [log (println (str "current-route: " current-route))
Moving the mouse makes this print like crazy. And re-frisk tells me, the only event triggered is: swap-logo ...which has two tries, one as fx event, the other as db event:
(re-frame/reg-event-fx
 ::swap-logo
 (fn [coeffects event]
   (let [db (:db coeffects)
         status (second event)]
     {:db (assoc-in db [:site-status :logo] status)})))

(re-frame/reg-event-db
 ::swap-logo2
 (fn [db]
   (let [status (-> db :site-status :logo)
         status (case status
                  :normal :highlighted
                  :highlighted :normal
                  :normal)]
     (assoc-in db [:site-status :logo] status))))
I guess this has to wait. I just hope, I don't need a complete rewrite. Cheers,

p-himik16:05:36

How is ::subs/current-route implemented?

bastilla17:05:57

Sub is

(re-frame/reg-sub
 ::current-route
 (fn [db]
   (get-in db [:data :user-status :current-route])))
which is set via
(re-frame/reg-event-db ::navigated
	(fn [db [_ new-match]]
		(let [old-match (a/get-current-route db)
					controllers (rfc/apply-controllers (:controllers old-match) new-match)]
			(a/set-current-route db (assoc new-match :controllers controllers)))))
and last snippet is
(defn set-current-route [db new-match]
	(assoc-in db [:data :user-status :current-route] new-match))
This is really old code. Maybe I should check all that again, but my government (aka girlfriend) calls for dinner. But I'll check this asap.

p-himik17:05:26

There is a very low chance of that code doing something wrong. But I just noticed that you didn't post the full code for common-sub-page, and that's the most important thing here. When a view gets re-rendered, it's almost certainly because of something going on within that particular view.

bastilla18:05:14

hm, alright. Sounds reasonable; and I'll have a look at it tomorrow morning, plus will post that missing snippet. In any case, a million thanks for checking my code and your precious time. I really appreciate your input, and the support really energizes and makes me feel less miserable, 🙂 .

bastilla18:05:38

PS: Here is the whole fn . I did comment out a lot of code in search for the reason behind all this behavior.

(defn common-sub-page []
  (reagent/with-let [current-route (re-frame/subscribe [::subs/current-route])]
    (fn []
      (when-let [current-route @current-route]
        (let [l (println (str "current-route: " current-route))
            sitemap-branch (a/get-sitemap-branch current-route nil)
      ;; sitemap-branch @(re-frame/subscribe [::subs/sitemap-branch])
            {:keys [component-key objects styles layout]} (get-sitemap-path-attrs sitemap-branch)
          ;; missing-objects (f/filter-missing-objs objects)
            ]
      ;; (if missing-objects
      ;;   (f/fetch-missing-objs missing-objects)
        (let [layout-panel (get-layout :layout-panel layout)
              html-id (:id layout)
              ids (conj component-key :layout-panel)
              sitemap-info (create-map ids sitemap-branch objects styles layout)]
          ;; (fn []
          (if layout-panel
            (if html-id
              [:span {:id html-id}
               [layout-panel sitemap-info]]
              [layout-panel sitemap-info])
            [:div "layout undefined"])))))))
              ;;  ))
              ;; )
Cheers

bastilla18:05:39

PPS: Everything behind (a/...) is just accessing app-db.

p-himik18:05:07

Sure, no problem. Hmm, still unclear. E.g. I don't know what get-layout or create-map or get-sitemap-path-attrs are. A hint - for a component to not be re-rendered, the returned Hiccup must be = to the previous value. (BTW I'm not an expert here, so take all my advice with a grain of salt) So if anything in that Hiccup is different (often caused by creating a function or a JS object in the view function), it will be re-rendered. Another reason why something is re-rendered is when its :key is changed.

bastilla12:05:16

I think, I can assure neither :key nor the hiccup gets altered. But I had a fresh look at those sources you (@U2FRKM4TW) mention. I think the problem is, I do de-reference the whole app-db later on. Via @rf-db/app-db. The app is highly dynamic and needs to read (not write) objects pointed to by other objects. And since I do not always have access to db via subscription I access app-db via @rf-db/app-db. And according to https://github.com/Day8/re-frame/issues/29, de-referencing (@) is problematic in terms of triggering unwanted re-rendering. (Check 8th post from top.) So (in my endless drive to go full retard) I thought about make my center subscription return the whole db, which then can be dynamically inspected for objects (pointed to by other objects). Like this:

(re-frame/reg-sub ::current-route
 (fn [db]
   (let [current-route (get-in db [:data :user-status :current-route])]
     [current-route db]))) ;; <--- Check last var
Question: Is this as foolish as it looks? Or could it be a way to randomly read the db without de-referencing (@) it later on?

p-himik12:05:55

Oh, yeah, just dereferencing something will definitely lead to a re-rendering as well. Also, don't use app-db directly - not only it leads to issues such as you describe, it's also a private API. > I do not always have access to db via subscription What does this mean, exactly? What prevents you from just adding a new subscription - one that, as you put it, dynamically inspects app-db for whatever objects?

bastilla13:05:50

I meant, as far as I understand the subscription signature, I cannot add an extra (random) param in order to query specific areas of app-db. That's where subscriptions seem to be limited: Just query pre-defined areas such as [:usr-data :images :logo :status], etc... But since my customers define their data themselves, there are areas in app-db that are blank to the application. (The app's knowledge ends at category level, e.g. [:data :objects].) And since I need to dynamically read objects outside subscriptions (which just points to pre-defined areas, AFAIK), I turned to directly reading @rf-db/app-db -- A no-no as you tell me. Everything could be solved, could I just return db from that initial subscription (see my snippet above). This certainly feels like overkill. But I don't know how else to solve that "dynamic/unknown areas" read demand.

p-himik13:05:07

> I cannot add an extra (random) param in order to query specific areas of app-db Eh?

(reg-sub :my-sub
  (fn [db [_sub-id & path-in-db]]
    (get-in db path-in-db)))

p-himik13:05:32

That's exactly what that FAQ entry warns about, but it doesn't say you can't do it.

p-himik13:05:16

If you know the path in advance - write it in a sub. If you don't know the path in advance - feed it to the sub. And you use the above sub as (subscribe [:my-sub :path :in :db]).

🙌 3
bastilla13:05:30

This is new information to me. (And I still need to deeply ponder what you're writing here.) You are refering https://day8.github.io/re-frame/FAQs/Inspecting-app-db/? At first glance I can't find that topic. But thanks! I have plenty of stuff to chew on now.

p-himik14:05:22

Oh, sorry - someone else linked that entry and I thought that was you because the topic is fundamentally the same. Here it is: https://day8.github.io/re-frame/correcting-a-wrong/#a-final-faq

p-himik14:05:19

To expand on what I've written - both subs and events are parameterized. You decide what to put in those parameters, be it some values, specific path components in app-db, some callback functions, DOM elements (although don't do this).

jahson05:05:54

Do all rerendered components use Layer 2 subscriptions?

bastilla16:05:44

@U071CG4QY thanks for chiming in, and good question. I'll go through all view fns again and do one proper style that's uniform across all views. (Just to be sure should this come up again.) The reason behind all this was exactly that de-referencing of app-db at a later point (-- ALL views did that eventually). And as @U2FRKM4TW pointed out, I should avoid private APIs. So that did the trick. I did some re-structuring of code which is way cleaner now. Also I reduced the returning of the whole app-db (in order to inspect) to only dicisive fractions of app-db (which is a subscription of is own now, as it should be.) The original sin of mine is certainly that I want to use re-frame in a way it's not build to be used. The technique @U2FRKM4TW showed above (`(subscribe [:my-sub :path :in :db])`) will certainly help to go beyond this. Cheers & thanks again to you guys!

👍 4
Ronny Li23:05:00

Hi everyone, I'm sure this question gets asked all the time but if I want to dispatch multiple events from a reg-event-fx do I simply provide a vector of vectors?

{:dispatch [[:event-1 nil] [:event-2 some-data]}

p-himik23:05:09

:fx or :dispatch-n, it's in the documentation.

🙏 3
Richie23:05:27

Hey. Why do I get the same result from both of these calls? The order of the interceptors is reversed between the two examples.

(:effects
 (re-frame.interceptor/execute
  [:thing]
  [(re-frame.std-interceptors/db-handler->interceptor #(assoc % :th 1))
   (re-frame.std-interceptors/path :p :a)]))
;; returns
;; {:db {:p {:a {:th 1}}}}
;; expected
;; {:db {:th 1}}

(:effects
 (re-frame.interceptor/execute
  [:thing]
  [(re-frame.std-interceptors/path :p :a)
   (re-frame.std-interceptors/db-handler->interceptor #(assoc % :th 1))]))
;; also returns
;; {:db {:p {:a {:th 1}}}}

p-himik23:05:10

Because you're using the private API incorrectly.

p-himik23:05:31

What is exactly the problem you're trying to solve?

p-himik23:05:06

(Private API is everything that's not in re-frame.core)

Richie00:05:34

Oh, ok. I have code in my event handler that computes a value from the db and then the event handler computes an effect from that value. I was trying to move the code that computes the value out of that function. Something like on-changes but in the :before position instead of :after. I’m just not sure how to refactor my code and I thought I’d try interceptors.

p-himik00:05:37

Sounds like you can simply compute that within the event handler itself, no?

p-himik00:05:08

The concrete problem in the scenario above is that db-handler->interceptor is a :before interceptor and path is both :before and :after. So path changes the :db effect in its :after phase.

Richie00:05:41

Yes, I can. I think the first thing that made me unhappy is seeing re-frame docs https://day8.github.io/re-frame/correcting-a-wrong/#a-final-faq criticizing hard coding db paths. In the view, I enjoy subscribing to data that doesn’t really exist in the db but is computed from the db. I was trying to put those two ideas together.

Richie00:05:05

Right. I do want to get {:db {:p {:a {:th 1}}}} out of it. I was surprised that I got the same result in both orderings. I don’t expect it to work with the handler interceptor first. The handler :before should run and following that the path’s :before and :after, right?

Richie00:05:07

I was motivated to post here because I see my understanding is wrong regarding how it works. I actually have the result I was trying to get…

Richie00:05:22

Thanks for taking the time. brb

p-himik00:05:04

That particular FAQ entry criticizes not hardcoding db paths but using db paths in your views. That's completely different. Two :before interceptors will run according to the order of the interceptors. Two :after interceptors will run in the reverse order. Any :before always runs before any :after. There was some nice pic in the documentation about it, IIRC. When in doubt, just read through re-frame source code (after you're done with the documentation, if you aren't that comfortable with reading code written by somebody else) - it's not that large, especially the relevant parts, and most of it is docstrings anyway. The most complex part is event queue IMO, and you don't need that part at all.

Richie01:05:12

I did read through the source before coming up with my example. I think I understand it pretty well. In the first expression where the interceptor list has the handler followed by the path, I expect the handler :before to happen before the path :before. Then I would see the handler assoc :th under :db and not the path.

p-himik10:05:55

Indeed that's what happens. But then :after of path puts in under that path.

Richie17:05:14

Ok, I think I have a better understanding now. I think what I was missing is that db-handler->interceptor writes the :coeffect :db into :effect :db. So, when I have interceptor chain where the handler precedes path then the path :before indexes nothing out of :coeffect :db and then the path :after grafts the :effect :db onto :effect :db.

Richie17:05:43

This code produces the result I originally expected to see for that example.

(:effects
 (re-frame.interceptor/execute
  [:thing]
  [ (re-frame/->interceptor :id :add-thing
                            :before #(assoc-in % [:coeffects :db :th] 1)
                            :after identity)
   (re-frame.std-interceptors/path :p :a)
   (re-frame.std-interceptors/db-handler->interceptor identity)]))
;; {:db {:th 1, :p {:a nil}}}

Richie17:05:55

Thanks for the help. I’m glad to have a better understanding. Now I’ll think about what is the problem I’m actually trying to solve.

👍 3