reagent

A minimalistic ClojureScript interface to React.js http://reagent-project.github.io/
zendevil 2021-03-10T14:07:06.002Z

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 2021-03-10T14:08:28.003100Z

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-himik 2021-03-10T14:17:16.003700Z

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 2021-03-10T15:18:16.004900Z

so having (reactify-component home) gives

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

zendevil 2021-03-10T15:18:28.005100Z

so what is the solution?

p-himik 2021-03-10T15:31:47.005300Z

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 2021-03-10T15:37:32.005700Z

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

p-himik 2021-03-10T15:39:46.005900Z

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

(defn home []
  ...)

zendevil 2021-03-10T15:42:18.006100Z

@p-himik that gives the following error:

Rendered more hooks than during the previous render

p-himik 2021-03-10T15:43:28.006300Z

No idea.

zendevil 2021-03-10T16:39:23.006500Z

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 2021-03-10T16:39:44.006700Z

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

p-himik 2021-03-10T16:41:02.006900Z

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

lilactown 2021-03-10T16:44:58.007100Z

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

lilactown 2021-03-10T16:45:51.007300Z

instead, treat home like a React component:

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

lilactown 2021-03-10T16:46:01.007500Z

this way you can use hooks inside of it

p-himik 2021-03-10T16:46:23.007700Z

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

lilactown 2021-03-10T16:48:57.007900Z

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

lilactown 2021-03-10T16:49:57.008100Z

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

zendevil 2021-03-10T16:51:53.008300Z

@lilactown 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

zendevil 2021-03-10T16:52:42.008500Z

I’m following the second answer in this to use onViewableItemsChange properly: https://stackoverflow.com/questions/48045696/flatlist-scrollview-error-on-any-state-change-invariant-violation-changing-on

lilactown 2021-03-10T16:53:01.008800Z

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

zendevil 2021-03-10T16:54:16.009Z

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

lilactown 2021-03-10T16:54:27.009200Z

that's frustrating

zendevil 2021-03-10T16:54:36.009400Z

so I wrapped it around useCallable

zendevil 2021-03-10T16:54:55.009600Z

and that’s where we’re at

lilactown 2021-03-10T16:56:09.009800Z

let's try what @p-himik 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])

lilactown 2021-03-10T16:56:39.010Z

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

lilactown 2021-03-10T16:56:54.010200Z

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

lilactown 2021-03-10T16:58:20.010400Z

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

zendevil 2021-03-10T17:12:36.010600Z

thanks @lilactown @p-himik

Clément Ronzon 2021-03-10T22:10:15.016400Z

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-himik 2021-03-10T22:19:24.016600Z

How is regular-cells used?

p-himik 2021-03-10T22:26:58.016800Z

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-himik 2021-03-10T22:28:29.017Z

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

Clément Ronzon 2021-03-10T22:30:13.017200Z

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

Clément Ronzon 2021-03-10T22:37:57.017400Z

I finally nailed it

p-himik 2021-03-10T22:38:52.017600Z

What was it?

Clément Ronzon 2021-03-10T22:40:52.017800Z

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-himik 2021-03-10T22:42:35.018Z

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-himik 2021-03-10T22:42:59.018200Z

Perhaps, you want to use into instead.

p-himik 2021-03-10T22:43:55.018400Z

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

Clément Ronzon 2021-03-10T22:46:30.018600Z

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

p-himik 2021-03-10T22:49:23.018800Z

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 Ronzon 2021-03-10T22:50:02.019Z

Oh ok, thank you for pointing that out 🙂