Fork me on GitHub

Suppose I have this component:

(defn panel [value]
  (r/with-let [f (fn []
                   (do-something-with value))]
    [:button {:on-click f}
     (create-str-label value)]))
If value changes, the button will be displayed with a new label. But f will still reference the old one, right? And to force it to reference the new value, I have to rerender the whole component, and the only way to do that would be to set :key from the outside and make it depend on value, right?


Just tested it - all assumptions seem to be correct, except for the "the only way" part. As an alternative, I can create a level of indirection via an intermediate ratom (or I guess it can or even should be a regular atom since I don't rely on it for rendering):

(defn panel [value]
  (r/with-let [inner-value (r/atom value)
               f (fn []
                   (do-something-with @inner-value))]
    (reset! inner-value value)
    [:button {:on-click f}
     (create-str-label value)]))
Looks ugly but at least it doesn't force me to come up with a way to derive a :key from a value which can be any value. Is it a known pattern? I don't think I've ever seen it documented anywhere.

Lucy Wang02:06:00

looks quite like the useCallback hook provded by native react

David Pham04:06:52

Why do you not create a function that takes inner value as argument?

David Pham04:06:30

And call f with the value on click?

David Pham04:06:43

Like {:on-click #(f @inner-value)}


@UEQGQ6XH7 It goes to show once again that I should stop working earlier in the day, when I can still think. :) Thanks! Creating a function in that way is exactly the patter that I've been using forever in other places, but here I just blanked on it.


But a solution with an extra atom might not be that bad if you have many such functions. Hmm.

David Pham06:06:31

Simple > ease

David Pham06:06:25

I guess it depends if you want to test your views. Because you could also give the functions as argument?


That wouldn't make sense in my case. I don't want to parameterize behavior.

David Pham06:06:23

Make a function that returns a map of function xD


I would say there's a fuzzy border separating the two approaches - the one with recreating functions and the one with wrapping the data in an extra atom. What to choose depends on the particular scenario. How to choose - hard to say, maybe it'll come with more experience of using the extra atom approach.

David Pham06:06:06

Probably as long as you hide the implementation, it is fine.

David Pham06:06:53

Actually I follow the same pattern of forms with subscriptions.

David Pham06:06:33

The reason is I don’t want the text field input to go through all the reframe mechanism, but still have the desire to control the content.

David Pham06:06:47

The additional atom acts as local state and cache.


Oh yeah, but the need in re-frame apps is a bit different. Also, apart from just avoiding extra data churning, it's a good pattern to control intermediate values when a user is still typing. And I think it's the only pattern to preserve the cursor position, but this one is implemented by Reagent itself, so you only need to think about it if you use some third-party components that Reagent doesn't know about.