Consider the following clojurescript code where the specter, reagent and re-frame frameworks are used, an external React.js grid component is used as a view 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}]})
In subs.cls
(register-sub
:cats
(fn [db]
(reaction
(select [ALL :data] (t/tree-visitor (get @db :cats))))))
result from select:
[{: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 code above works as expected.
Instead of displaying the data with the react.js component I want to go through each of the maps in the :cats vector and display the :text items 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 output:
ROOT
Objects
Version
X1
How do I loop through a subscribed collection in re-frame and display the data as a list-item? ( = question for title ).
To summarize, there are 4 major approaches to loop through a collection in general. Lists have 10 approaches because of a) the extra specialized iterator and b) able to retrieve elements directly given its position in the loop. What is Java Collections Framework?
We declare a list of strings and populate it. The enhanced for loop is the first way and most direct. Next is the classic for loop, which uses an iterator. for (Iterator<String> it = strList.iterator (); it.hasNext (); ) { Notice that there is no increment/decrement in this classic for loop.
@NorbertBoros Not just this method, but any method which enumeratesthe list (e.g. foreach), and the list changes, will throw the same exception. A forloop does not enumeratethe list. – Maarten Apr 21 at 10:51 Add a comment | Your Answer Thanks for contributing an answer to Stack Overflow!
2 Should be noted that if the List is changed, you will get "Collection was modified; enumeration operation may not execute." with this method. – Mecanik Feb 8 at 10:33 2 @NorbertBoros Not just this method, but any method which enumeratesthe list (e.g. foreach), and the list changes, will throw the same exception.
First, be clear why you use key
...
Supplying a key
for each item in a list is useful when that list is quite dynamic - when new list items are being regularly added and removed, especially if that list is long, and the items are being added/removed near the top of the list.
keys
can deliver big performance gains, because they allow React to more efficiently redraw these changeable lists. Or, more accurately, it allows React to avoid redrawing items which have the same key as last time, and which haven't changed, and which have simply shuffled up or down.
Second, be clear what you should do if the list is quite static (it does not change all the time) OR if there is no unique value associated with each item...
Don't use :key
at all. Instead, use into
like this:
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
(into [:ul] (map #(vector :li (:text %)) @cats))])))
Notice what has happened here. The list provided by the map
is folded into
the [:ul]
vector. At the end of it, no list in sight. Just nested vectors.
You only get warnings about missing keys when you embed a list
into hiccup. Above there is no embedded list
, just vectors
.
Third, if your list really is dynamic...
Add a unique key
to each item (unique amoung siblings). In the example given, the :text
itself is a 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 result in a list
which is the 1st parameter to the [:ul]
. When Reagent/React sees that list
it will want to see keys
on each item (remember lists are different to vectors in Reagent hiccup) and will print warnings to console were keys
to be missing.
So we need to add a key
to each item of the list
. In the code above we aren't adding :key
via metadata (although you can do it that way if you want), and instead we are supplying the key
via the 1st parameter (of the [:li]
), which normally also carries style data.
Finally - part 1 DO NOT use map-indexed
as is suggested in another answer.
key
should be a unique value associated with each item. Attaching some arb integer does nothing useful - well, it does get rid of the warnings in the console, but you should use the into
technique above if that's all you want.
Finally - part 2 there is no difference between map
and for
in this context.
They both result in a list
. If that list
has keys then no warning. But if keys are missing, then lots of warnings. But how the list was created doesn't come into it.
So, this for
version is pretty much the same as the map
version. Some may prefer it:
(defn categorymanager []
(let [cats (re-frame/subscribe [:cats])]
(fn []
[:div
[:ul (for [i @cats] [:li {:key (:text i)} (:text i)])]])))
Which can also be written using metadata like this:
(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 issue:
https://github.com/Day8/re-frame/wiki/Using-%5Bsquare-brackets%5D-instead-of-%28parentheses%29#appendix-2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With