Fork me on GitHub
#reagent
<
2021-08-05
>
lilactown00:08:03

I'm elbow deep in some re-frame/reagent machinery and could use some insight if anyone has some

lilactown00:08:24

what I'm seeing is that when I deref a re-frame subscription, dependent subscriptions are run twice

lilactown01:08:05

(rf/reg-sub
  :greeting
  (fn [_ [_ name]]
    (js/console.log "in greeting")
    (str "Hello, " name)))

(rf/reg-sub
  :excited-greeting
  :<- [:greeting]
  (fn [greeting _]
    (str (string/capitalize greeting) "!")))

@(rf/subscribe [:excited-greeting])
this prints "in greeting" twice

lilactown01:08:22

now, this changes if I wrap the deref of the subscription in a ratom context:

(binding [reagent.ratom/*ratom-context* #js {}]
  @(rf/subscribe [:excited-greeting]))
this only prints "in greeting" once 😵

lilactown01:08:23

now I'm writing a React hook to use with reactions/subscriptions, so this is relevant for app development since there's not a ratom-context set when rendering a plain React component

lilactown01:08:54

I could set one like I have, but I really have no idea what effect it has other than not double computing on initial computation

emccue01:08:23

dumb question, but what is the motivation for this?

lilactown01:08:45

using reagent reactions in an app that doesn't use reagent for creating components

lilactown01:08:57

also, initially I wrote the hook to use add-watch & remove-watch to subscribe to the reaction. however, changes were not propagated until I wrapped it in a *ratom-context*

lilactown01:08:08

I don't mind wrapping it but I'm worried I may be introducing a memory leak somewhere. the reaction code is such a hairy nest it's hard for me to keep track of what uses which

p-himik06:08:03

Regarding double subscription evaluation - probably this? https://github.com/day8/re-frame/issues/657

lilactown16:08:37

this is exactly what I'm dealing with yes

yiorgos11:08:00

Hi guys, I am completely beginner with Reagent and I was wondering how do I translate the React props of a component to reagent functions? For example

function MyDialog() {
  let [isOpen, setIsOpen] = useState(true)

  return (
    <Dialog open={isOpen} onClose={() => setIsOpen(false)}>
    </Dialog>
  )
}
This component has isOpen prop and a callback that changes the state how can I write that in idiomatic reagent?

alexdavis11:08:13

(defn my-dialog
  []
  (let [open-state (r/atom true)]
    (fn []
      [:> Dialog
       {:open @open-state
        :onClose #(reset! open-state false)}])))
Assuming Dialog is imported like
["my-dialog-npm-thing" :refer [Dialog]]

alexdavis11:08:39

you can use useState but I don't see the point and don't think its idiomatic

yiorgos11:08:41

Sweet, thank you very much!

p-himik11:08:07

And you can replace let with r/with-let to avoid having to wrap the Hiccup in another (fn [] ...).

👍 5
wcalderipe13:08:34

Hey folks, I have a question. I'm extending a React library to have a Tooltip and a Button element. The Tooltip documentation says: > Please ensure that the child node of Tooltip accepts onMouseEnter, onMouseLeave, onFocus, onClick events. So, if I wrap the Button in a function to create a Reagent component, the tooltip doesn't work. Does someone know how to explain to me why, please?

;; Here the tooltip work on the button
  [:> Tooltip {:title "Foo Bar"}
   [:> Button "Hover me!"]]

  (defn wrap-button [text]
    [:> Button text])

  ;; But here the tooltip doesn't work
  [:> Tooltip {:title "Foo Bar"}
   [wrap-button "Hover me!"]]

javi13:08:24

try

[:> Tooltip {:title "Foo Bar"}
   (r/as-element [wrap-button "Hover me!"])]

p-himik13:08:28

It might be because the underlying library clones the React element and adds those events to it.

p-himik13:08:48

@U9VP9VCE6 Reagent does that automatically since it's all Hiccup anyway.

wcalderipe13:08:29

> It might be because the underlying library clones the React element and adds those events to it. If that's the case, shouldn't it work out of the box?

wcalderipe13:08:49

I mean, under the hood wrap-button is a React component

p-himik13:08:15

No, because it doesn't exist in the actual DOM and it doesn't propagate its props to Button.

p-himik13:08:26

I would just go through the source code of Tooltip to be sure. The answer is definitely there.

wcalderipe14:08:15

Thanks

👍 3
lilactown18:08:59

hmm, I cannot think of a way to make reagent reactions work w/ React concurrency w/o introducing the double compute problem w/ re-frame subs, leaking memory or showing an initial nil value

lilactown18:08:28

(defn use-reaction2
  [reaction]
  (let [inner-reaction (hooks/use-ref nil)
        cb (hooks/use-ref identity)]
    (hooks/use-subscription
      (hooks/use-memo
        [reaction]
        {:get-current-value
         (fn []
           (if-some [ir @inner-reaction]
             @ir
             (ra/run-in-reaction
               #(deref reaction)
               inner-reaction
               "current"
               (fn [_] (@cb))
               {})))
         :subscribe (fn [callback]
                      (reset! cb callback)
                      #(r/dispose! @inner-reaction))}))))
this is my current iteration. the problem this version has is if the component calling this gets rendered with a reaction it hasn't seen before (i.e. mounting), and then that render is thrown away (so it doesn't run :subscribe or the cleanup function returned by it) then the inner-reaction that gets created in :get-current-value won't be disposed

lilactown18:08:52

which will leak memory, since it will persist in the watchers of the reaction passed in

lilactown18:08:38

this doesn't happen in the current stable version because it synchronously renders components, but w/ React Concurrency it may interleave renders based on different events and end up throwing away a render that is determined to be unusable after committing a different render

lilactown18:08:52

oh also that code is horribly broken hmm

lilactown18:08:37

(defn use-reaction
  [reaction]
  (let [inner-reaction (hooks/use-ref nil)
        cb (hooks/use-ref identity)]
    (hooks/use-subscription
      (hooks/use-memo
        [reaction]
        {:get-current-value
         (fn []
           (if (some? @inner-reaction)
             @reaction
             (ra/run-in-reaction
               #(deref reaction)
               inner-reaction
               "current"
               (fn [_] (@cb))
               {})))
         :subscribe (fn [callback]
                      (reset! cb callback)
                      #(r/dispose! @inner-reaction))}))))
the reaction constructed by make-reaction I guess never gets its state updated, it's always nil

lilactown18:08:11

the correct thing to do is dereference the original reaction

lilactown18:08:32

this essentially replicates what Reagent components do on render within get-current-value

lilactown19:08:12

this might be remediated in by using a finalizer to dispose of the reaction on GC

lilactown19:08:13

you could have a sentinel object which has a reference to the reaction returned by run-in-reaction, and when that object is GC'd call dispose! on that reaction

lilactown20:08:06

oh the reason that the state of the reaction created by run-in-reaction was nil is because it needs to have _run called on it

lilactown20:08:11

this is all so confusing 😵