Fork me on GitHub
#helix
<
2021-10-08
>
lilactown15:10:10

yeah, the default behavior of React (and thus Helix) is to rerender even if it's given similar data

lilactown15:10:41

reagent does this a little differently, it memoizes by default.

lilactown15:10:16

I would just profile your app and memoize the components that are slow, and see if that helps

JonR15:10:14

K, will do thanks @lilactown

JonR15:10:18

Related to that, do you generally pass things like ids as props or maps/objects? One of my go to optimizations is to do stuff like select keys on sequences and just pass ids and basic data types as props. Then in my child components use hooks with those props to hydrate more complete objects. More of a general React question I guess but just curious about your thoughts and if helix favors a particular strategy

JonR15:10:56

Sorry, one more question. When I use {:wrap [(helix.core/memo)]} the same issue happens even with basic props of a few int ids. I have to use {:wrap [(helix.core/memo =)]} to have it act as expected which makes me think I'm not understanding the basic memo if the props maps are always referentially different...

lilactown15:10:31

a couple things I think will help: 1. helix.core/memo has a bug in it where it doesn't correctly default compare props when used with factory functions 2. React memoization by default requires you to think about reference equality, not value equality

lilactown15:10:57

I'm actually not sure what helix.core/memo's default behavior should be with factory functions

lilactown15:10:24

but while I'm thinking about that, you can write your own comparison function, like

(defn identical-factory-props?
  [prev cur]
  (let [prev-props (:helix/props prev)
        cur-props (:helix/props cur)]
    (identical? prev-props cur-props)))
which will check to see if the maps you pass in are identical?.

lilactown15:10:18

or, if you are constructing a new map every render but want to check to see if each value within it is identical:

(defn factory-props-kvs-identical?
  [prev cur]
  (let [prev-props (:helix/props prev)
        cur-props (:helix/props cur)]
    (and (= (count prev-props) (count cur-props))
         (every?
          #(identical? (get prev-props %) (get cur-props %))
          (keys cur-props)))))

lilactown15:10:11

or you can use =, it might be fast enough!

lilactown15:10:36

can't really make any solid recommendations w/o profiling your app

JonR16:10:19

sweet, thanks for the feedback. Gonna try this stuff now and see where I get

lilactown16:10:24

> Related to that, do you generally pass things like ids as props or maps/objects? One of my go to optimizations is to do stuff like select keys on sequences and just pass ids and basic data types as props. Then in my child components use hooks with those props to hydrate more complete objects. More of a general React question I guess but just curious about your thoughts and if helix favors a particular strategy This can work. I'm not sure yet how I would compare them but another option is to memoize the expensive computations themselves. This means that you'll still render the components on each change, but the expensive parts will only be run if the data the parts care about change

lilactown16:10:00

it depends on if you're storing your data outside the react tree (which it sounds like you are, if you're able to hydrate them via ID and listen to changes) or inside the react tree

lilactown16:10:31

it's the difference between computing the expensive thing every render, and relying on subscription to external sources:

(defnc my-component
  [{:keys [entity-id]}]
  (let [entity (subscribe [:entity entity-id])
        derived-info (expensive-to-compute (:name entity) (:foo entity))]
   ,,,))
or only computing the expensive thing when the things in entity we care about have changed:
(defnc my-component
  [{:keys [entity]}]
  (let [derived-info (hooks/use-memo
                      [(:name entity) (:foo entity)]
                      (expensive-to-compute (:name entity) (:foo entity)))]
   ,,,))

lilactown16:10:09

which creating new elements can be included in this, if rendering some child component is really expensive then you can memoize it in its parent too....

(defnc my-component
  [{:keys [entity]}]
  (d/div
   (hooks/use-memo
    [(:name entity) (:foo entity)]
    ($ expensive-component-to-render
       {:name (:name entity)
        :foo (:foo entity)}))))