helix

lilactown 2021-09-06T00:02:32.077300Z

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

lilactown 2021-09-06T00:03:04.078Z

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

lilactown 2021-09-06T00:05:08.079800Z

(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)

lilactown 2021-09-06T00:07:20.081500Z

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 =)]}

lilactown 2021-09-06T00:09:19.082800Z

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.

lilactown 2021-09-06T00:12:27.083700Z

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.

lilactown 2021-09-06T00:13:42.084700Z

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

lilactown 2021-09-06T00:18:36.087300Z

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)))

lilactown 2021-09-06T00:20:10.087500Z

opened a bug here: https://github.com/lilactown/helix/issues/89

az 2021-09-06T00:23:03.089400Z

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