Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso

Clojurescript - Om - Dynamic Components and Unique Keys

As I get older my tolerance for javascript seems to be getting worse so I’ve been employing clojurescript as a shield from the true horrors of javascript. I’ve been playing around wih om which acts as an interface to facebook’s react.

While developing with om, I kept getting the same javascript error after rendering a dynamic list like below:

If you have done any developing with om, then I am confident in saying that you will have come across this warning after rendering such a list:

Each child in an array should have a unique “key” prop. Check the renderComponent call using <tbody>. See http://fb.me/react-warning-keys for more information.

Here is the code I was using to render such a list:

old.cljs
1
2
3
4
5
6
7
8
9
10
11
12
(defn clips-view [{:keys [clips]} owner]
  (reify
    om/IRender
    (render [this]
      (html/html
        [:div.well
         [:table.table.table-bordered.table-hover.table-striped
          [:tbody
           (if (empty? clips)
             [:tr
              [:td.text-center {:colSpan "2"} "No Clips!"]]
             (om/build-all clip-view clips))]]]))))

Line 12 in the above is the villain of this piece as there is no way that I could find of passing a func that will set the key property that react uses to identify each dynamic child.

I have been trying to ignore this warning as it did not cause any code to stop executing but I kept seeing this question pop up on the irc channel and I could not find a good answer on the google.

It also turns out that not supplying a react key for each dynamic item could lead to some very unexpected behaviour.

After consulting the om docs I discovered that om/build can take a third :opts argument and one of the allowed keys is a :react-key which is one of the solutions to the problem.

Armed with this information, I refactored the above code to the following:

refactor.cljs
1
2
3
4
5
6
7
8
9
10
11
12
(defn clips-view [{:keys [clips]} owner]
  (reify
    om/IRender
    (render [this]
      (html/html
        [:div.well
         [:table.table.table-bordered.table-hover.table-striped
          [:tbody
           (if (empty? clips)
             [:tr
              [:td.text-center {:colSpan "2"} "No Clips!"]]
             (map-indexed #(om/build clip-view %2 {:react-key %1}) clips))]]]))))

The only change is on line 12:

1
(map-indexed #(om/build clip-view %2 {:react-key %1}) clips))

I have used the simplest case of an index for the :react-key but if I was rendering from a list where each item had a unique identifier then I would use the :key option to specify an element property that would be bound as the react key and would look something like this:

b.clj
1
(map #(om/build clip-view % {:key :id}) clips))

(thanks to Anna Pawlicka) for mentioning this in the comments below.

Or probably the best option of is to pass an :opts map containing a :key option to om/build-all:

a.clj
1
(om/build-all clip-view clips {:key :id})

Thanks to Daniel Szmulewicz for mentioning this on twitter.

I can now paste a link to this post when somebody asks how to get rid of this warning on the clojurescript irc channel.

Comments