reagent

A minimalistic ClojureScript interface to React.js http://reagent-project.github.io/
2021-01-01T14:33:23.465500Z

I keep getting this warning on my FlatList component (react native), however I know my list element component is pure. Is there any way to tell react that it's a PureComponent through reagent?

p-himik 2021-01-01T14:36:26.465800Z

Just as an idea - you can try memoization of the relevant view functions.

p-himik 2021-01-01T14:37:36.466Z

Of course, even if it works it would bring a new problem - how to remove items from the cache. FWIW, https://github.com/ptaoussanis/encore has memoization with a TTL and/or max number of entries.

p-himik 2021-01-01T14:39:40.466400Z

Of perhaps a better way would be to just do what PureComponent does: > React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

juhoteperi 2021-01-01T14:40:24.466600Z

Reagent components need state for RAtom implementation, so no, PureComponent is not possible. But Reagent components already implement shouldComponentUpdate.

p-himik 2021-01-01T14:41:53.466800Z

Thanks! Then, do I understand it correctly that using memoization or your own version of shouldComponentUpdate (if that's even possible) or something like that won't change anything?

juhoteperi 2021-01-01T14:42:04.467Z

Or you could also create "pure" React components and use them inside Reagent components, just remember you can't use Ratoms directly in these components: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#pre-10-workaround

juhoteperi 2021-01-01T14:44:53.467400Z

It is possible to use custom shouldComponentUpdate, Reagent default impl checks the component arguments using Cljs equals method, which works efficiently for Cljs datastructures, JS doesn't have such method normally: https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/component.cljs#L164-L179 but you could e.g. just compare certain fields in your own impl.

1πŸ‘
juhoteperi 2021-01-01T14:47:07.467700Z

Back to the original problem. Not sure how react-virtualized detecs if a component is slow. If the arguments are pure and not changing, could be that all the list items follow some Ratom which is changing? Reactions (or Re-frame subscription) could be used to fix those cases.

juhoteperi 2021-01-01T14:49:14.468Z

E.g.

(defn list-item [foo]
  (let [active? (= (:id foo) @active-item)] ...))
is slow, because every list-item is re-rendered if @active-item changes.
(defn list-item [foo]
  (let [active? @(rf/subscribe [:active-item? (:id foo)])] ...))
Is fast, because re-render is triggered only if the result from the reaction changes.

2021-01-01T14:58:34.468200Z

I am already using a re-frame subscription to get the data which is used to render the list elements. I'm not using ratoms at all

juhoteperi 2021-01-01T15:00:39.468500Z

Re-frame subscriptions are ratoms

juhoteperi 2021-01-01T15:01:46.468700Z

Maybe "reaction" would be batter name, but I mean using any Reagent reactive data structure or calculation. r/atom r/reaction r/track and rf/subscription all share the implementation.

1πŸ‘
juhoteperi 2021-01-01T15:02:54.469Z

And the example holds with subscriptions,

(defn list-item [foo]
  (let [active? (= (:id foo) @(rf/subscribe [:active-item-id]))] ...))
Triggers re-render for all items, while the second example wouldn't.

2021-01-01T15:03:54.469200Z

Here is the relevant code

juhoteperi 2021-01-01T15:04:51.469600Z

js->clj is slow

juhoteperi 2021-01-01T15:05:33.469800Z

Can you also paste ::subs/get-pokemon

juhoteperi 2021-01-01T15:06:33.470Z

and info-card

2021-01-01T15:08:20.470200Z

2021-01-01T15:08:31.470600Z

info-card on line 99 https://pastebin.com/SAU1rkwx

2021-01-01T15:13:37.471Z

I am already using as-element when rendering info-card

juhoteperi 2021-01-01T15:14:16.471200Z

Try

juhoteperi 2021-01-01T15:14:24.471400Z

(defn render-card [obj idx _separators]
  (let [item (.-item obj)]
    (r/as-element [info-card (assoc item :index idx)])))

(defn- seperator []
  [:> rn/View {:style (:separator styles)}])

(defn- convert-to-array [vec]
  (let [arr #js []]
    (doseq [x vec]
      (.push arr x))
    arr))

;; :> converts properties recursively to 
;; r/create-element doesn't convert props from clj -> js
(defn card-list []
  (r/create-element
    rn/FlatList
    ;; Not sure if convert-to-array is required. Cljs should implement similar methods to Array,
    ;; but might not be enough for RN.
    #js {:data (convert-to-array @(rf/subscribe [::subs/get-pokemon]))
         :getItemLayout (fn [_ index]
                          ;; Never use clj->js if you have map literal, #js is MUCH faster.
                          #js {:length 120
                               :offset (* 120 index) :index index})
         :initialNumToRender 6
         :ItemSeparatorComponent (r/reactify-component seperator)
         :keyExtractor (fn [item-obj idx] (str (:id item-obj)))
         :progressViewOffset true
         ;; Memoize probably doesn't help here.
         :renderItem render-card}))

juhoteperi 2021-01-01T15:14:52.471600Z

Important parts being replacing clj->js with #js and using r/create-element instead of :>

juhoteperi 2021-01-01T15:15:22.471800Z

Here the :data won't be recursively converted to JS objects, so you don't need to convert it back to Cljs in render-card

1πŸ‘
juhoteperi 2021-01-01T15:18:59.472200Z

And I checked RN doesn't care if you use PureComponent or anything, it just checks how long the render calls take: https://github.com/facebook/react-native/blob/46be292f671c70aac4ecc178c96e3a2a6a3d16da/Libraries/Lists/VirtualizedList.js#L1564-L1582

2021-01-01T15:23:28.472500Z

oh lol, yeah this seems way snappier. Thanks!