Fork me on GitHub
#clojurescript
<
2021-07-06
>
Simon07:07:59

What is the recommended approach for passing a lot of arguments to a reagent component? Right now I pass them as a normal function

[:div
     (navbar)
     [:h1 "Title"]
     [multiargument-component
        geolocation
        net-worth-thousands
        (.parseInt js/Number (get @app-state "age"))
        annual-savings
        withdrawal-rate
        annual-return]]
This components' arguments get passed down to several other components, where I need to keep track of their arguments. Sometimes i might change annual-savings to monthly-savings , but then i need to update every intermediate component in the component tree until i reach the component that will actually implement the change. So i'm thinking that it would make sense to pass down a map instead with all the needed arguments something like this:
[:div
     (navbar)
     [:h1 "Title"]
     [multiargument-component
      {:geolocation geolocation
       :net-worth-thousands net-worth-thousands
       :age (.parseInt js/Number (get @app-state "age"))
       :annual-savings annual-savings
       :withdrawal-rate withdrawal-rate
       :annual-return annual-return}]]
Then when i make a change i only need to change the annual savings in the topmost component and then in the child component that consumes the argument. The con would be that all child components would get all arguments, which might cause too many unneeded arguments to be passed around.. but then again it is a map, so AFAIK it would not impact performance, since it is only passing down the reference. What would you recommend?

Lu10:07:38

Having the props in a map is definitely miles better.

Lu10:07:31

However there’s a problem with passing down many props. Performance wise you will have many re-renderings even if you don’t use the “props that change” in your component

👍 2
Lu10:07:45

to understand this you can simply copy and paste this code in your file

Lu10:07:46

(def counter (r/atom 0))

(defn counter-component
  [{:keys [label]}]
  (prn "re-rendering")
  [:p label])

(defn wrapper []
  [:div
   [:button
    {:on-click #(swap! counter inc)}
    "Increase counter"]
   [counter-component {:counter @counter
                       :label "counter label"}]])

(defn main-view
  []
  [wrapper])

Lu10:07:19

you will notice that although the counter is never used in counter-component, you will get the “re-rendering” logs every time the counter increases

Lu10:07:33

try to then remove the counter key from the map and you see that the component never re-renders

Lu10:07:13

To wrap it up just make sure that you pass as params only what the component actually needs vs the world 🙂

p-himik10:07:35

Note that calling a view function != re-rendering. counter-component function will be re-executed. But the component will not be re-rendered because the Hiccup vector [:p label] is the same.

Lu11:07:04

Right I see! But still there’s performance cost in doing that operation

p-himik11:07:21

Indeed. But I usually don't care about shallow trees with few leaves - doesn't matter if it's 5 ms or 10 ms. I have never measured anything like it though - I only measure when I perceive some delay that I would like to get rid of.

🙌 2
p-himik11:07:32

So as usual, it's a trade-off without a clear answer where the actual answer depends on the context.

Simon13:07:07

Thanks for the thoughtful answers. I think I will go with a map and not worry too much about passing too many props in the map. If i start to experience performance issues, I will look into splitting up the map before passing it on to its children where relevant.

👍 4