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!
@mjacobus the wrapping in helix/props is only done using an experimental helix feature that is not enabled by default
Ah, got it. Reading out of context. Thanks.
can you share some code so I understand what you’re trying right now?
Thanks for the offer. I don’t have a simplified example, but I’ll try to share parts if that works.
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?
(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))))
yes, that’s correct
is cell-comp
the memoized component?
(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}))))
Yes, cell is memoized
Thanks for looking. Sorry about the mess due to debugging, experimenting, and being a novice with cljs.
I would make sure that your cell-comp
s aren’t being remounted every render
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
this would be due to some funkiness with the :key
you’re associating
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.
oh, also you’re passing in =
to react/memo
don’t do that
that might be what’s breaking it
Ah. Good point. That ends up being a deep comparison. But that should still work, no?
the values passed into react/memo
will be previousProps
and newProps
as arguments, which are JS objects dynamically created each render
since they’re JS objects, =
falls back to identical?
which will always be false
Got it.
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
I removed the = to react/memo, but that didn’t fix the problem.
Actually I was wrong. That did fix it. Great eagle eyes. I really appreciate the help.
sure thing!
that is confusing.
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
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.
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
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
yeah that makes sense. the docs in hx
used to be wrong
maybe still are 😅
https://github.com/Lokeh/helix/commit/1320832fde832da00bff2bbac3704ef6f4aff24c
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
I’m not sure why I had to wait 10 minutes for that feature. I’d appreciate it in 5 next time.
Thanks again. That’s pretty great.