helix

https://github.com/Lokeh/helix
Matt Jacobus 2020-03-19T21:15:29.177800Z

Hi, I’ve been experimenting with helix and really like it. My current experiment is one where I’m attempting to get rid of unnecessary rerenders by using use-callback for my callback and react.memo to wrap my component. So far I’ve been unsuccessful. I have many of instances of this wrapped component and when any one of them changes in the same container, they all rerender. All the props passed to a component that shouldn’t rerender are identical? (including the callback) so I expected it work. BTW, reading above about how helix wraps my props cljs map in a js map under the “helix/props” key, I’m not sure that react.memo would work properly, so that could be the problem. Am I right about this? Any hints about what I might be doing wrong? Thanks in advance!

lilactown 2020-03-19T21:18:04.178300Z

@mjacobus the wrapping in helix/props is only done using an experimental helix feature that is not enabled by default

Matt Jacobus 2020-03-19T21:18:41.179Z

Ah, got it. Reading out of context. Thanks.

lilactown 2020-03-19T21:18:52.179400Z

can you share some code so I understand what you’re trying right now?

Matt Jacobus 2020-03-19T21:21:46.180700Z

Thanks for the offer. I don’t have a simplified example, but I’ll try to share parts if that works.

Matt Jacobus 2020-03-19T21:22:35.181500Z

Quick question: should I expect that if my props are identical?that my component will generally not rerender, or am I on a wild goose chase?

Matt Jacobus 2020-03-19T21:27:32.183900Z

(defnc body-comp [{:keys [cells-2d swapped update-sizes]}]                                                           
 (let [g-ref (hooks/use-ref nil)
    sizes-ref (hooks/use-ref {})
    update-cell-sizes (hooks/use-callback :once (fn [id g s] (swap! sizes-ref assoc id [g s])))
    ids-2d (map (partial map cell-id) cells-2d)]
  (hooks/use-layout-effect                         
   :always
   (let [gsizes-2d (map (partial map @sizes-ref) ids-2d)
      sizes-2d (map (partial map second) gsizes-2d)
      [widths heights] (->> (text/max-sizes cells-2d sizes-2d)
                 add-margin)]
    (update-sizes [sizes-2d widths heights])
    (layout-cells cells-2d gsizes-2d widths heights)))
  (log "render body")
  (d/g
   {:ref g-ref} 
   (mapcat (partial map (fn [cell id]
              ($ cell-comp {:key id :id id :cell cell
                     :update-sizes update-cell-sizes}))) 
       cells-2d ids-2d))))

lilactown 2020-03-19T21:27:34.184Z

yes, that’s correct

lilactown 2020-03-19T21:29:45.186400Z

is cell-comp the memoized component?

Matt Jacobus 2020-03-19T21:30:01.186800Z

(defnc cell-comp [{:keys [id cell update-sizes] :as props}] 
 {:wrap [(react/memo =)]}
 (log "cell" id cell update-sizes props)
 (let [id-ref (hooks/use-ref id)
    cell-ref (hooks/use-ref cell)
    update-sizes-ref (hooks/use-ref update-sizes)
    g-ref (hooks/use-ref nil) 
    sizes-ref (hooks/use-ref nil) 
    update-child-sizes (hooks/use-callback :once #(reset! sizes-ref %))]
  (log "cell identical?"
     (identical? id @id-ref) 
     (identical? cell @cell-ref) 
     (identical? update-sizes @update-sizes-ref))
  (hooks/use-layout-effect
   :always 
   ;; this fires after we have g-ref and sizes-ref
   (update-sizes id @g-ref @sizes-ref))
  (d/g
   {:ref g-ref} 
   ;; TODO make this pluggable 
   ($ text/text-comp
    {:id id :class "cell" :cell cell :gref g-ref
     :update-sizes update-child-sizes}))))

Matt Jacobus 2020-03-19T21:30:08.187Z

Yes, cell is memoized

Matt Jacobus 2020-03-19T21:31:29.187900Z

Thanks for looking. Sorry about the mess due to debugging, experimenting, and being a novice with cljs.

lilactown 2020-03-19T21:32:45.188300Z

I would make sure that your cell-comps aren’t being remounted every render

lilactown 2020-03-19T21:34:01.189500Z

in that case, they would show their props as identical? to what’s stored in the ref - because the refs are reset on re-mount - and would not be memoized

lilactown 2020-03-19T21:34:13.189900Z

this would be due to some funkiness with the :key you’re associating

Matt Jacobus 2020-03-19T21:36:25.191500Z

Great advice. I didn’t think of remounting. If so, it sounds like this is likely not anything to do with helix, but likely just a bug in my interaction with react. Thanks again for taking a look.

lilactown 2020-03-19T21:36:31.191700Z

oh, also you’re passing in = to react/memo

lilactown 2020-03-19T21:36:32.191900Z

don’t do that

lilactown 2020-03-19T21:36:58.192300Z

that might be what’s breaking it

Matt Jacobus 2020-03-19T21:37:42.193800Z

Ah. Good point. That ends up being a deep comparison. But that should still work, no?

lilactown 2020-03-19T21:37:44.193900Z

the values passed into react/memo will be previousProps and newProps as arguments, which are JS objects dynamically created each render

lilactown 2020-03-19T21:38:12.194800Z

since they’re JS objects, = falls back to identical? which will always be false

Matt Jacobus 2020-03-19T21:38:13.194900Z

Got it.

lilactown 2020-03-19T21:38:56.195700Z

if you pass in no custom comparator to react/memo, it will call the equivalent of identical? on each prop which is what you want here

Matt Jacobus 2020-03-19T21:39:36.196600Z

I removed the = to react/memo, but that didn’t fix the problem.

Matt Jacobus 2020-03-19T21:40:42.197100Z

Actually I was wrong. That did fix it. Great eagle eyes. I really appreciate the help.

lilactown 2020-03-19T21:40:50.197300Z

sure thing!

lilactown 2020-03-19T21:40:55.197500Z

that is confusing.

lilactown 2020-03-19T21:42:16.199800Z

I should probably have a helix.core/memo helper that would do the marshalling of the JS props object to a CLJS structure, like it does for the body of defnc, so that you can pass in = for deep equality or whatever other custom comparator you want

Matt Jacobus 2020-03-19T21:43:52.201100Z

Now that I see the docs for React.memo, I see that it gets the whole props map. Agree that marshalling to a cljs structure in a helper would be perfect.

Matt Jacobus 2020-03-19T21:48:36.202500Z

BTW, I’m realizing now that I grabbed the react/meme =from the hx (old?) doc. Somehow google led me here. https://cljdoc.org/d/lilactown/hx/0.5.2/doc/how-to

Matt Jacobus 2020-03-19T21:50:48.203400Z

It is old. My fault again. I see now that helix has the correct code mentioned here. https://github.com/Lokeh/helix/blob/master/docs/creating-components.md

lilactown 2020-03-19T21:59:03.203800Z

yeah that makes sense. the docs in hx used to be wrong

lilactown 2020-03-19T21:59:14.204100Z

maybe still are 😅

lilactown 2020-03-19T22:05:16.205100Z

I created helix.core/memo that can be used in place of react/memo . I’ll document it and do a release later this week

Matt Jacobus 2020-03-19T22:07:27.205800Z

I’m not sure why I had to wait 10 minutes for that feature. I’d appreciate it in 5 next time.

😆 3
Matt Jacobus 2020-03-19T22:07:38.206100Z

Thanks again. That’s pretty great.