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])])