3

I had the following code:

(ns my.app
  (:require [reagent.core :as r]))

(def data (r/atom {}))

(defn child-component
  [group-title group-data]
  some-hiccup)

(defn parent-component []
  [:div
    (for [[group-title group-data] @data]
      [child-component group-title group-data])])

(js/window.addEventListener "load"
  #(r/render [parent-component]
     (js/document.getElementById "some-id"))
  false)

It was exhibiting unexpected behavior: when I made certain updates to data, Reagent would call the components' renderers but not update the UI, while other changes would cause parent-component to be remounted rather than simply updated and each child-component to be rendered from scratch. In no case would it minimally update existing components the way React is supposed to.

I guessed that the problem had something to do with for, so I played around with some alternatives. I tried wrapping for in doall, tried coercing the seq into a vector, and even tried wrapping for in a let and moving the @data deref to the let binding vector just in case the problem was related to my derefing the r/atom in the for binding vector. None of those changes made any difference.

Since I know in advance the keys of the map stored in data, I was able to try the following, and it worked:

(defn parent-component []
  [:div
    (let [data-map @data]
      (list
        [child-component :title-1 (:title-1 data-map)]
        [child-component :title-2 (:title-2 data-map)]))])

Although this solved my problem in this case, it's unsatisfactory as a general solution, because you won't always know the shape of data that will be stored in your r/atom. So, finally I discovered a solution that works for the general case:

(defn parent-component []
  (reduce
    (fn [prev [group-title group-data]]
      (conj prev [child-component group-title group-data]))
    [:div]
    @data))

So my problem is solved, but I don't understand why. If the problem was just that I needed to make the original seq eager, why didn't doall do the trick? Either I'm making some really stupid mistake, or there's something about the inner workings of Reagent that I don't understand.

Note: I'm also managing the r/atom in an idiosyncratic way, which relates to the comment I made above about how Reagent behaved differently depending on how I updated the r/atom. However, I am not sure if that would be relevant to my general question about using reduce vs doall. Let me know if it is, and I'll provide some details on that.

**Edit**

I figured it out! The problem was my use of :key, which I neglected to mention above. The original parent-component looked like this:

(defn parent-component []
  [:div
    (for [[group-title group-data] @data]
      ^{:key (gensym)}
      [child-component group-title group-data])])

Maybe this is something about React that I don't understand, but it seems that you shouldn't use gensym to generate the :key. I changed to the following, and everything works fine:

(defn parent-component []
  [:div
    (for [[group-title group-data] @data]
      ^{:key group-title}
      [child-component group-title group-data])])
grandinero
  • 1,155
  • 11
  • 18
  • Take a look at http://stackoverflow.com/a/33458370/597473. It explains the role of `^{:key...}`. – Piotrek Bzdyl Oct 31 '16 at 13:13
  • That discussion doesn't seem to explain why I would have a problem with `(gensym)`. I do understand why and when `^{:key ...}` is used, but if the only requirement is that each key be unique, I don't understand why `(gensym)` wouldn't do the trick. Is it because `(gensym)` gets called again each time Reagent calls the rendering function, thus leading Reagent to believe it's a distinct element? – grandinero Oct 31 '16 at 14:33
  • I guess this might be the case. You can replace `(gensym)` with your wrapper function that will log to console every time its called before it returns the result of `(gensym)` to verify. – Piotrek Bzdyl Oct 31 '16 at 14:35

0 Answers0