How do I loop through a signed collection in a repeated frame and display the data in a list?

Consider the following clojurescript code, which uses specter, reagent, and re-frame frames, using the external component of the React.js grid as a presentation component.

In db.cls:

(def default-db {:cats [{:id 0 :data {:text "ROOT" :test 17} :prev nil :par nil} {:id 1 :data {:text "Objects" :test 27} :prev nil :par 0} {:id 2 :data {:text "Version" :test 37} :prev nil :par 1} {:id 3 :data {:text "X1" :test 47} :prev nil :par 2}]}) 

At subs.cls

 (register-sub :cats (fn [db] (reaction (select [ALL :data] (t/tree-visitor (get @db :cats)))))) 

Result:

 [{:text "ROOT", :test 17} {:text "Objects", :test 27} {:text "Version", :test 37} {:text "X1", :test 47}] 

In views.cls

 (defn categorymanager [] (let [cats (re-frame/subscribe [:cats])] [:> Reactable.Table {:data (clj->js @cats)}])) 

The above code works as expected.

Instead of displaying data with the react.js component, I want to go through each of the maps in the vector: cats and display: text elements in html ul / li.

I started as follows:

 (defn categorymanager2 [] (let [cats (re-frame/subscribe [:cats])] [:div [:ul (for [category @cats] ;;--- How to continue here ?? --- ) )) 

Expected Result:

 ROOT Objects Version X1 

How do I loop through a signed collection in a repeated frame and display the data in a list? (= question for the name).

+5
source share
3 answers

At first , it’s clear why you are using key ...

A key supply for each item in the list is useful when this list is quite dynamic - when new items in the list are regularly added and removed, especially if the list is long and items are added / removed near the top of the list.

keys can provide more performance because they allow React to redraw these pluggable lists more efficiently. Or, more precisely, it allows React to avoid redrawing elements that have the same key as the last time, and which have not changed and which are simply shuffled up or down.

Second , it’s clear what you should do if the list is pretty static (it doesn't change all the time) OR if there is no unique value associated with each element ...

Do not use :key at all. Instead, use into as follows:

 (defn categorymanager [] (let [cats (re-frame/subscribe [:cats])] (fn [] [:div (into [:ul] (map #(vector :li (:text %)) @cats))]))) 

Pay attention to what happened here. The list provided by map is folded into vector [:ul] . At the end of it there is no list. Just nested vectors.

You only get warnings about missing keys when you insert list into hiccups. There is no inline list above, just vectors .

Third , if your list is really dynamic ...

Add a unique key to each element (unique siblings). In the above example itself :text is good enough key (I assume it is unique):

 (defn categorymanager [] (let [cats (re-frame/subscribe [:cats])] (fn [] [:div [:ul (map #(vector :li {:key (:text %)} (:text %)) @cats)]]))) 

That map will lead to list , which is the first parameter for [:ul] . When Reagent / React sees that list , it will want to see the keys for each element (remember that the lists are different from the vectors in the hiccup reagent) and to print warnings on the console there were keys that would be absent.

Therefore, we need to add key to each list element. In the above code, we do not add :key through metadata (although you can do it this way if you want), and instead we supply key through the 1st parameter ( [:li] ), which usually also contains style data.

Finally - Part 1 DO NOT use map-indexed as suggested in another answer.

key must be a unique value associated with each element. Attaching some integer arb is nothing useful - well, it gets rid of warnings in the console, but you should use the into technique above if that's all you want.

Finally, part 2 in this context there is no difference between map and for .

Both of them lead to list . If this list has keys, then no warnings. But if there are no keys, then there are many warnings. But the way to create a list is not in it.

So, this for version is pretty much like the map version. Some may prefer this:

 (defn categorymanager [] (let [cats (re-frame/subscribe [:cats])] (fn [] [:div [:ul (for [i @cats] [:li {:key (:text i)} (:text i)])]]))) 

What can also be written using metadata, for example:

 (defn categorymanager [] (let [cats (re-frame/subscribe [:cats])] (fn [] [:div [:ul (for [i @cats] ^{:key (:text i)}[:li (:text i)])]]))) 

Finally - Part 3

mapv is a problem because of this problem: https://github.com/Day8/re-frame/wiki/Using-%5Bsquare-brackets%5D-instead-of-%28parentheses%29#appendix-2

+20
source

Edit: for a much more consistent and technically correct explanation of the keys and map see Mike Thompson's answer!


Here is how I wrote it:

 (defn categorymanager2 [] (let [cats (re-frame/subscribe [:cats])] (fn [] [:div [:ul (map-indexed (fn [n cat] ;;; !!! See /questions/1248991/how-do-i-loop-through-a-subscribed-collection-in-re-frame-and-display-the-data-as-a-list-item/3993994#3993994 !!! ^{:key n} [:li (:text cat)]) @cats)]]))) (defn main-panel [] [:div [categorymanager2]]) 

A few points:

  • See the re-frame read preview section near the end, which says:

    Subscriptions

    can be used only in Form-2 components, and the subscription should be in the external configuration function, and not in the internal rendering function. So wrong (compare with the correct version above) ...

    • Therefore, your component was "wrong" because it did not wrap the renderer inside an internal function. Readme has all the details, but in short, not wrapping the rendering of a component depending on the subscription inside the internal function is bad, because it causes the component to re-emit when the db changes - not what you want! You want the component to only restart when the subscription changes.
  • Edit: Seriously, see Mike Thompson's answer . For some reason, I prefer to use map to create seq Hiccup tags, you can also use a for loop, but the critical point is that every [:li] Hiccup vector needs an entry :key in its metadata, which I add here, using the index of the current category in the @cats vector. If you don't have :key , React will complain to the Dev Console. Please note: this key must somehow associate this @cats element with this tag: if the cats subscription changes and shuffles, the result may not be what you expect, because I just used this very simple key. If you can guarantee that the category names will be unique, you can simply use the value :test or the value :test or something else. The fact is that the key must be unique and must uniquely identify this element.
    • (NB: don't try to use mapv to make a tag vector. Hiccup-re-frame hates this. There must be seq , like what map creates.)
  • I also included the main-panel example to emphasize that
    • parent components do not need the subscriptions that their child component needs, and that
    • you should call categorymanager2 component with square brackets instead of a function with parsers (see Using [] instead of () ).
+2
source

Here is an ul / li example:

 (defn phone-component [phone] [:li [:span (:name @phone)] [:p (:snippet @phone)]]) (defn phones-component [] (let [phones (re-frame/subscribe [:phones])] ; subscribe to the phones value in our db (fn [] [:ul (for [phone in @phones] ^{:key phone} [phone-component phone] @phones)]))) 

I grabbed this code from this remake tutorial .

In addition, map preferable to for when using a reagent. There is a technical reason for this, I just do not know what it is.

+1
source

All Articles