Fork me on GitHub
#helix
<
2021-09-06
>
lilactown00:09:32

two key insights: 1. helix.core/memo without a comparator does an identical? check on the new props and previous props. so if you're reconstructing your data every render, but the data is the same value, this will render more often than you want 2. the implementation of node-by-name reconstructs the node tree every time it is called. since that's how you're reading the tree from the db value, you're guaranteed to always get a new object reference even if the value is equal, thus breaking the memoization

lilactown00:09:04

the reason that the same db code works in reagent is because reagent uses = instead of identical? when comparing new and previous props

lilactown00:09:08

(FWIW the identical? check is just what plain React does, helix doesn't change the behavior at all. reagent does diverge by using = always. the reason to not use = by default is that it can be slow for the exact case you're running into: a deeply nested structure which are equal but not the same reference)

lilactown00:09:20

the easy fix is to pass a custom comparator to helix.core/memo:

(defnc node-view
  [{:keys [node update-name-handler] :as props}]
  ;; use `=` here to compare the props maps using Clojure equality,
  ;; rather than the default behavior of checking to see if each prop
  ;; is identical
  {:wrap [(helix.core/memo =)]}

lilactown00:09:19

however to me this is a bit unsatisfying, because if your node tree grows huge than = could end up being an actual bottleneck in your app's performance. probably not the case rn, maybe you wouldn't run into it, but it's something I think a lot about since helix sits at the bottom of your application.

lilactown00:09:27

another way you could try to fix this is by memoizing node->tree. this has the benefit of maintaining object references between changes, at the expense of memory.

lilactown00:09:42

an LRU cache or something else could be used, but at that point we're getting pretty fancy. but that would be like, the enterprise-grade, optimal performant way of solving this, probably. I haven't tried it tbh

lilactown00:09:36

OK I think you have also succumbed to a bug in helix, which is that when we wrap the body of the component in the HOC you provide, the name we give the inner function is the name of the component. psuedo-code:

(def node-view (-> (fn node-view [props]
                     ;; this refers to the inner fn, not the top-level def node-view!!
                     ($ node-view ,,,))
                   (helix.core/memo)))

az00:09:03

Thanks so much for all this great feedback @lilactown makes perfect sense. Going to play with this some more. Very cool that we can pass in custom memo fn