Fork me on GitHub
#reagent
<
2021-03-10
>
zendevil.eth14:03:06

I’m passing in the following component as a screen (react navigation) like so:

[:> (.-Screen Stack) {:name :Feed :options {:headerShown false}
                           :component (as-element [home])}]
where home is the following:
(defn home [{:keys [navigation] :as props}]
  (dispatch [:load-videos 1])
  [safe-area-view {:backgroundColor :white :flex 1}
   [:<>
    (if-not @(subscribe [:videos-loaded])
      ;; gif requires fresco library for android
      [image {:source (js/require "../resources/images/loading.gif")
              :style {:top 80 :left -115 :resizeMode :cover}}]
      [flat-list {:refreshControl
                  (as-element
                   [refresh-control {:refreshing @(subscribe [:refreshing])
                                     :onRefresh #(dispatch [:load-videos 1])}])
                  :data @(subscribe [:videos])
                  :renderItem (fn [item]
                                (as-element
                                 [video-press (get (js->clj item) "item")
                                  navigation]))
                  :keyExtractor (fn [item]
                                  (.-uri item))
                  :initialNumberToRender 4
                  :maxToRenderPerBatch 2
                  :windowSize 5
                  :onViewableItemsChanged (.-current (useRef (fn [viewable-items changed] (js/console.log "viewable items " viewable items
                                                                                               "changed " changed))))
                  ;;#(dispatch [:load-if-at-last %1 %2])
                  :style {:paddingLeft 10
                          :paddingRight 10}}])]])
However, I’m getting the following error:
Got an invalid value for 'component' prop for the screen 'Feed'. It must be a valid react component.
How can I fix this?

zendevil.eth14:03:28

also I want to use

home
as a functional component because it contains the useRef hook. So my guess is that
(as-element [:f> home]) 
should work. But I’m getting the same error in this case as well

p-himik14:03:16

React component != React element. as-element creates a React element. I'm assuming, of course, that :component is named aptly and expects a React component (ah, right - the error explicitly states so).

zendevil.eth15:03:16

so having (reactify-component home) gives

Invalid hook call. Hooks can only be called inside of the body of a function component

zendevil.eth15:03:28

so what is the solution?

p-himik15:03:47

The warning speaks for itself, I think. home has to be a proper function component. Given that you cannot use reactify-component with a function component, I see two options: - Wrap home in a regular Reagent component and use reactify-component on it - Use the old way to create a proper function component: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#pre-10-workaround

zendevil.eth15:03:32

@U2FRKM4TW what do you mean by “Wrap `home` in a regular Reagent component”?

p-himik15:03:46

(defn wrapped-home []
  [:f> home])

(defn home []
  ...)

zendevil.eth15:03:18

@U2FRKM4TW that gives the following error:

Rendered more hooks than during the previous render

zendevil.eth16:03:23

I changed the useRef hook to useCallable, and now I get the same error:

Invalid Hook call. Hooks can only be called inside of the body of a function component...
This suggests that the all the parents of the component that contains the hook must be functional.

zendevil.eth16:03:44

In that case we can’t use reactify-component at all

p-himik16:03:02

> all the parents of the component that contains the hook must be functional This is false.

lilactown16:03:58

@U01F1TM2FD5 calling reactify-component on home won't work, since Reagent will create a class component to wrap home

lilactown16:03:51

instead, treat home like a React component:

(defn home [props]
  (dispatch [:load-videos 1])
  (as-element
   [safe-area-view ,,,]))

lilactown16:03:01

this way you can use hooks inside of it

p-himik16:03:23

That was my very first message in this thread. :)

lilactown16:03:57

I was thinking you would then pass home in directly to the Screen Stack. the extra layer and reactify-component to convert props makes sense tho

lilactown16:03:57

@U01F1TM2FD5 another thing I noticed: you're using useRef here to wrap the callback passed to onViewableItemsChanged. why is that?

zendevil.eth16:03:53

@U4YGF4NGM This is what I have now:

(defn home [{:keys [navigation] :as props}]
  (dispatch [:load-videos 1])
  (as-element [safe-area-view {:backgroundColor :white :flex 1}
   [:<>
    (if-not @(subscribe [:videos-loaded])
      ;; gif requires fresco library for android
      [image {:source (js/require "../resources/images/loading.gif")
              :style {:top 80 :left -115 :resizeMode :cover}}]
      [flat-list {:refreshControl
                  (as-element
                   [refresh-control {:refreshing @(subscribe [:refreshing])
                                     :onRefresh #(dispatch [:load-videos 1])}])
                  :data @(subscribe [:videos])
                  :renderItem (fn [item]
                                (as-element
                                 [video-press (get (js->clj item) "item")
                                  navigation]))
                  :keyExtractor (fn [item]
                                  (.-uri item))
                  :initialNumberToRender 4
                  :maxToRenderPerBatch 2
                  :windowSize 5
                  :onViewableItemsChanged viewable-ref
                  ;;#(dispatch [:load-if-at-last %1 %2])
                  :style {:paddingLeft 10
                          :paddingRight 10}}])]]))
after wrapping with as-element, it seems that the :videos-loaded subscription inside isn’t working

lilactown16:03:01

yes, apologies. if you're passing home in like this then it won't subscribe to your re-frame subs

zendevil.eth16:03:16

basically I was trying to do in the flatlist:

:onViewableItemsChange #(dispatch […])
But this gives https://stackoverflow.com/questions/48045696/flatlist-scrollview-error-on-any-state-change-invariant-violation-changing-on

lilactown16:03:27

that's frustrating

zendevil.eth16:03:36

so I wrapped it around useCallable

zendevil.eth16:03:55

and that’s where we’re at

lilactown16:03:09

let's try what @U2FRKM4TW suggested with a couple modifications:

(defn home [{:keys [navigation] :as props}]
  (let [on-viewable-items-change (.-current (useRef ,,,))]
    (dispatch [:load-videos 1])
    [safe-area-view {:backgroundColor :white :flex 1}
     [:<>
      (if-not @(subscribe [:videos-loaded])
        ,,,)]]))

(defn wrapped-home [props]
  [:f> home])

lilactown16:03:39

the error you were seeing before about "Rendered more hooks than during the previous render" was due to having the useRef call inside of the if expression

lilactown16:03:54

hooks calls must be static across renders - you cannot put them inside loops or conditionals

lilactown16:03:20

the wrapped-home can be reactify-component and passed in like you did before

Clément Ronzon22:03:15

Hey gals and guys, What is the easiest way to debug a component in order to understand why it gets re-rendered please? (I read https://github.com/reagent-project/reagent/blob/master/doc/WhenDoComponentsUpdate.md) This is my component:

(defn- regular-cells
  [& {:keys [entity]}]
  (js/console.log "regular-cells" entity)
  [:div
   {:on-click #(do (js/console.log "clicked")
                   (dispatch [:my.ns.events/clicked entity]))}
   (str entity)])
When I click on the div, it has the effect of sending a PUT request to an API, then a GET to that same API which returns a new value for entity. Here is what I get in the logs:
regular-cells {:id 1955, :enabled false}
clicked
regular-cells {:id 1955, :enabled false}
regular-cells {:id 1955, :enabled false}
regular-cells {:id 1955, :enabled true}
I don't understand why it gets re-rendered 3 times instead of 1 after clicked if the only param hasn't changed?

p-himik22:03:24

How is regular-cells used?

p-himik22:03:58

If it's used as (regular-cells ...), then replace () with []. If it's used with [], then it's probably because the :on-click function get recreated on each render of regular-cells. I don't know all the inner workings of this particular issue, but there have been numerous discussions about it. I believe there are hooks that fix it, but I'm not sure whether using them for such a simple component would be worth it. It could also be fixed by using a cache manually, which is also more trouble than it might be worth in this particular case.

p-himik22:03:29

If you confirm that it's indeed the latter, you can @ lilactown - I remember him having this discussion with someone else recently.

Clément Ronzon22:03:13

Thank you @U2FRKM4TW, I'll check how regular-cells and continue my investigation 🙂

Clément Ronzon22:03:57

I finally nailed it

p-himik22:03:52

What was it?

Clément Ronzon22:03:52

I had something like this:

(into [:div]
         (cond-> [[regular-cells :entity @entity]]
                       @show? (merge [:div "..."] [:div "..."])))
And moving the @show? ... upper in the DOM fixed it. I'm not 100% sure why.

p-himik22:03:35

Not sure either. But this particular code makes me squint in suspicion. The contract of merge says that it's to be used only with maps. But you're passing it vectors. I have no clue what you want to get out of it, but it's definitely not a correct usage.

p-himik22:03:59

Perhaps, you want to use into instead.

p-himik22:03:55

(cond-> [:div [regular-cells ...]]
  @show? (conj [:div ...] [:div ...]))

Clément Ronzon22:03:30

Using into instead of merge if for sure a better practice. It doesn't resolve the issue though (tested)

p-himik22:03:23

Sure, I didn't mean that it would help with the initial problem. It's just that using merge on anything other than maps is an undefined behavior.

Clément Ronzon22:03:02

Oh ok, thank you for pointing that out 🙂