Consider the following reagent component. It uses a ref function, which updates a local state atom, based on the real size of a span element. This is done in order to re-render the component displaying its own size
(defn show-my-size-comp []
(let [size (r/atom nil)]
(fn []
(.log js/console "log!")
[:div
[:span {:ref (fn [el]
(when el (reset! size (get-real-size el))))}
"Hello, my size is:" ]
[:span (prn-str @size)]])))
If the implementation of get-real-size
returns a vector, the log message is printed constantly, meaning the component unnecessarily being re-rendered all the time. If it returns just a number or a string, the log appears only twice - as intended in this scenario.
What's the reason for this? Is it maybe that updating an clojure script atom with a new vector (containing the same values though) internally means putting another JavaScript object there, thus changing the atom? Whereas putting a value produces no observable change? Just speculation...*
Anyways - for the real use case, saving the size of the span in a vector would certainly better.. Are there ways to achieve this?
I cam across this, when trying to enhance the answer given in this question.
* since in JS: ({} === {}) // false
You can work around the problem with a not=
check:
(fn [el]
(when el
(let [s (get-real-size el)]
(when (not= s @size)
(reset! size s)))))
I'm not sure what the reason is for why vectors should differ from other values.
I think I have an answer for why vector behaves differently from string/number. Reagent counts a reagent atom as "changed" (and thus updates a component that depends on it) when identical?
returns false between the old and the new values. See the subhead "changed?" in this tutorial:
For ratoms, identical? is used (on the value inside the ratom) to determine if a new value has changed with regard to an old value.
However, it turns out that identical?
behaves differently for vectors and for strings/ints. If you fire up either a clj or a cljs repl, you'll see that:
(identical? 1 1)
;; true
(identical? "a" "a")
;; true
(identical? [1] [1])
;; false
(identical? ["a"] ["a"])
;; false
If you look at what identical?
does here, you'll see that it tests if its arguments are the same object. I think the underlying internal data representation is such that, in clojure, "a" is always the same object as itself, whereas two vectors containing the same value are not the same object as one another.
Confirmation: with ordinary rather than reagent atoms, we can see that string identity is preserved across atom resets, while vector identity is not.
(def a1 (atom "a"))
(let [aa @a1] (reset! a1 "a") (identical? aa @a1))
;; true
(def a2 (atom ["a"]))
(let [aa @a2] (reset! a2 ["a"]) (identical? aa @a2))
;; false