This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-10-20
Channels
- # announcements (2)
- # babashka (1)
- # bangalore-clj (1)
- # beginners (5)
- # cider (16)
- # clojure (125)
- # clojure-spec (3)
- # clojure-uk (5)
- # clojurescript (27)
- # cursive (46)
- # data-science (3)
- # dirac (2)
- # emacs (11)
- # fulcro (2)
- # graphql (4)
- # luminus (2)
- # nrepl (1)
- # pathom (15)
- # re-frame (1)
- # reagent (52)
- # shadow-cljs (149)
- # sql (11)
- # tools-deps (11)
- # xtdb (14)
When are the bindings within reagent.core/with-let
re-evaluated?
I have a component like
(defn c [x y z]
(reagent/with-let [selected (reagent/atom nil)
select! #(reset! selected %)]
[... something that uses x, y, and z]))
The issue is that when y
changes, the old selected
value is preserved.Hmm. It's likely just my misunderstanding of Rea{ct|gent}.
The Reagent documentation sometimes uses a notion of "component instance". And the with-let
docstring says: "when used in a component the bindings are only evaluated once". I assume it means "once per component instance".
But what does this "component instance" mean exactly? Namely, when does something rendered stops being one instance and becomes a different instance?
I know that it happens when :key
changes. Previously, I thought that it also happens when both the arguments of the instance change and its parent is re-rendered. Apparently, it's wrong, and I'm surprised I figured that out just now. I have no idea how it hasn't affected me previously.
Does it mean that, whenever I have something in with-let
(or in the outer let
of a form-2 component) that depends on the outside value, I must make the :key
of the instances of that component depend on that value?
@p-himik yes, or alternatively if you need to re-compute some derived data based on props, do the computation in the render fn
the form-2/with-let and “component instance” is all circling around when React’s “mount” lifecycle runs
React only mounts a component again when the parent component mounts, or when it thinks that the previous component instance needs to be invalidated (e.g. your key
example)
Thanks! Yeah, I just read a bit on how React reconciles DOM trees. Apparently, just because of how it does that, I have never stumbled upon this issue before.
yeah. reagent tries to make things easier by not giving us explicit control over the lifecycle of a component, but we end up with edge cases like “I want to subscribe to something based on a prop” that become hard to do
one option would be to invalidate the React element by using key
(I think this is really hacky)
also potentially bad for perf because it will cause a re-mount of every child component as well
option 2 would be to use a form-3 component and something like getDerivedStateFromProps
or componentDidMount
depending on what you’re trying to do
apparently the React team suggest the key
hack in some cases: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-controlled-component
In an ideal world, IMO, there should be a macro similar to with-let
that would just re-evalute its bindings whenever the values it depends on change.
you would have to analyze the code in the with-let bindings to find any usage of props, and writing the React lifecycles would be difficult
with React Hooks, it covers a lot of the edge cases here. E.g.: - Running side effects on prop change
(defnc c [{:keys [x y z]}]
;; run `do-side-effect` only on different values of `y`
(react/useEffect do-side-effect #js [y]))
- Only run expensive computations on prop + state change
(defnc c [{:keys [x y z]}]
(let [[state set-state] (react/useState 0)]
(react/useMemo #(expensive x state) #js [x state])))
the only one it doesn’t cover is the one I think you want, which is to just completely reset the state of the component on some prop change
I see a lot of people use key
for running side effects and such which could really be fixed by better code practices
Hey there, currently looking at an older Reagent project and was wondering if anyone has more context around this change: https://github.com/reagent-project/reagent/commit/ce1486a7cd4b52b9763885f33de60daeeabbdb94 Although it was possible before this commit, I assume we shouldn't use swap!/reset! on reactions directly? I think my confusion mainly arises from the comparisons to cursors in the documentation. What's the best way to handle the use case where you want an atom to react to some value, while also being able to update it elsewhere? Rather than resetting/swapping a value on the reaction itself my understanding is that you should keep the value in a separate ratom and update it using the reaction's on-set. Is there a better way to handle this that doesn't require both a reaction and separate atom?
@mazin Reactions are for purely derived values. If you want to swap!
/ reset!
a reaction, you should define an on-set
function when you make the reaction which updates whatever is derived from
otherwise, you’re likely to get into an inconsistent state. If you were to swap! or reset! the value of a reaction directly, and then the states that were derived updated, the Reaction would update again and the new state would probably be inconsistent with the state that was swapped or reset directly
I’m curious what your use-case is that you want to be able to update a derived value without updating its sources
I think the use-case is mainly wanting to initialize to some value that is not available immediately and setting up a reaction to understand when that value becomes available. Ex: an input that you want to initialize to some value but that value is not present immediately so you use the reaction as the model which the input then calls swap! or reset! on on changes.
hmm by “derived values are unlikely to update after initialization” do you mean the “source atoms are unlikely to update after initialization”?
so if I understand: - you have some value that isn’t immediately available (e.g. some network req needs to resolve) - you have some UI state that needs to be initialized to this value, and then be uncontrolled ?
im not sure what you mean by uncontrolled. - yes, the value is not immediately available - the value is used to initialize an input model, whose value can then update as the user types
one way to workaround that commit is to use a reaction that reacts to that value, then use a separate ratom for the input model. then updating that input model ratom within the initial value on reaction on-set
(and throw away anything that is currently potentially stored in the input model).
uncontrolled meaning that the state of the UI after initialization is not controlled by props
so you want something like:
(defn my-input [{:keys [default]}]
(let [input-state (r/atom default)]
(fn [_]
[:input {:default-value default :on-change #(reset! input-state (.. % -target -value)) :value @input-state}])))
(defn my-form []
(let [data (go-fetch-data)]
(fn []
(if (:initial-name @data)
[my-input {:default (:initial-name @data)}]
[:div "Loading..."]))))
in the above example we route around this issue by not rendering the dependent my-input
until we have the data to initialize it
you could do something more clever if you really want to encapsulate this loading state inside my-input
, but it’s not straight forward
yeah, thanks, that makes sense. is using reactions directly considered an anti-pattern?
using reaction is not an anti-pattern, but their usage is more advanced than your every day UI concerns IMO
Using React key to force reinitialized could work also:
[:input {:key (if (nil? default-value) 1 2) :default-value default-value}]
When default-value
chenges from nil
to the real value, element identity changes, old element is removed from tree and new replaces it, and because the element is new, the new default value is used.@juhoteperi right, p-himik and I discuss that above
in this case, I’m guessing the input isn’t editable until you have the initial value? so I think that rendering a separate UI for that is better than using the key
trick
Yeah, depends on the use case. One could use read-only prop also etc.