Hey 🙂 I have this piece of code (ugly, I know, but bear with me!) that I can't seem to get working as intended.
(def state (r/atom ""))
(def duplicate (fn [] (r/atom @state)))
(defn on-change
[]
(let [curr (duplicate)]
(fn [e]
(reset! curr (-> e .-target .-value)))))
(defn attributes
[]
(let [curr (duplicate)]
{:placeholder "what"
:value @curr
:on-change (on-change)}))
(defn app []
[:div [:input (attributes)]])
The thing I'm trying to do is make "duplicate" reactive to the r/atom "state" up until the point I start typing in the input in which point my text overrides this "reactiveness".
What really happens is that "duplicate" is reactive to "state" but I my on-change function doesn't seem to affect it and it isn't reseted.
Does any one know why this happens? what am I missing?
Please assume that all the extra functions I made are pretty necessary - unless they are the root cause.(def state (r/atom nil))
(def duplicate (fn [] (r/atom @state)))
(defn on-change
[v]
(fn [e]
(print "ONCHANGE")
(reset! v (-> e .-target .-value))))
(defn input [v]
(print "INPUT")
[:input {:placeholder "what"
:value @v
:on-change (on-change v)}])
(defn stateful-input
[]
(print "STATEFUL-INPUT")
(let [curr (duplicate)]
[input curr]))
(defn app []
(print "APP")
[:div [stateful-input]])
This example works as expected.
Here only stateful-input register to changes in state through duplicate.
NOTE: since the ratom created in duplicate is not derefed in stateful-input, changes to that ratom wont effect stateful-input and won't cause creation of another duplicate ratom.
Then input get passed the created ratom and pass it to on-change and deref it (register to changes to it).
The effect:
changes to state propagate all the way down and will override any value currently in duplicate.
changes to duplicate affects only the existing duplicate ratom and doesn't propagate back to state and doesn't cause a recreation of duplicate ratom.(def state (r/atom nil))
(def duplicate (memoize (fn [] (print "DUPLICATE")
(let [dup (r/atom nil)]
(r/track! #(reset! dup @state))
dup))))
(defn on-change
[v]
(fn [e]
(print "ONCHANGE")
(reset! v (-> e .-target .-value))))
(defn input []
(print "INPUT")
(let [dstate (duplicate)]
[:input {:placeholder "what"
:value @dstate
:on-change (on-change dstate)}]))
(defn display []
[:p @state])
(defn app []
(print "APP")
[:div
[display]
[input]])
I re-did the example which more resembles the original problem (no passing of props).
This also works as expected, but this time:
on first execution, duplicate creates a reactive dependency to state through track!.
it is then memoized which prevents recreations of dup.
it works the same as before:
1. changes to state propagates to dup
2. changes to dup doesn't cause recreation of dup
the problem with this example is the use of track!, according to the docs use of track! implies a use of dispose! but I'm not sure where it fits here and what happens if dispose! isn't called.
It feels like some kind of memory leak that might be a problem in a full scale app.
If anyone knows what are the repercussion of using track! without dispose! I'll be happy to learn.when you call (r/atom @state) in duplicate, you are creating a new atom with the current value of what is in state. any changes to this new atom will not propagate back to state
so I think your code creates new atoms every render? not sure why you would want that. what are you trying to achieve?
I'm not trying to propagate back to state but the other way around, which meaning that if state changes duplicate will change reactively to it (which does work).
What I'm trying to achieve is let's pretend that this state atom changes after a fetch call, I want the value inside duplicate to change with it unless I've started typing inside the input in which case the "reactiveness" is gone.
I'd do it like this
(def state (r/atom {:default ""
:typed ""}))
(defn on-change
[]
(fn [e]
(swap! curr assoc :typed (-> e .-target .-value))))
(defn input
[]
(let [{:keys [default typed]} @state]
[:input
{:placeholder "what"
:value (if (clojure.string/blank? typed)
default
typed)
:on-change on-change}]))
(defn app []
[:div
[input]])
otherwise if you are declaring atoms within subcomponents, you will have to watch out for issues https://github.com/reagent-project/reagent/issues/371Thanks but that's a bit problematic. I already have an atom from a different source that I just wanna "duplicate". I can't create them both together as the same atom with a map.
I'm not sure why you couldn't model it in one atom with a map, with different subkeys. maybe there is a different way you could do it here https://github.com/reagent-project/reagent/blob/master/doc/ManagingState.md
Does anyone know the underlying reason why isn't it working? Is it related to react state handling?
Something like react's useEffect could have helped here
@itai the reason the original doesn't do anything is because a new atom is being created in on-change, and that atom is updated and then thrown away.