re-frame

reefersleep 2024-03-20T11:44:51.865149Z

Hello guys I would post this in #reagent as it pertains to vanilla reagent, and not re-frame, but there's very little interaction in there, and I think people here might have an idea of what I'm getting at 🙂 I've got a vanilla reagent application and a bunch of developers who prefer using local r/atoms for everything. I've also got a desire to make our components generally "pure" in that they should take args for everything. They can hold local state, but it must somehow be passed down from up top. I've been thinking that we could perhaps passs cursors in order to have our cake and eat it, too. Something like the below. I assume there's prior art for something like this? Constraints reiterated; • uses only vanilla reagent • wants to maintain internal state • but passed down from global state • wants to be as pure as possible (side effectful things are set up via params)

(defn example-component
  [{:keys [...a-bunch-of-data-args...
           state-cursor]}]
  (let [initial-state   (-> {:numclicks 0}
                            (setup-stuff-based-on ...a-bunch-of-data-args...))]
    (r/create-class
      {:display-name           "example-component"
       :constructor            (fn [_] (reset! state-cursor initial-state))
       :reagent-render
       (fn [_]
         [:div
          [:div "Number of clicks: " (:numclicks @state-cursor)]
          [:button {:on-click (swap! state-cursor update :numclicks inc)}
           "Click me!"]
          ;;Imagine a bunch of other stuff that interacts with 'state-cursor' here :)
          [:button {:disabled (= initial-state @state-cursor)
                    :on-click #(something)}
           "Proceed"]])})))

p-himik 2024-03-20T11:50:23.610849Z

Both #reagent and this channel have roughly the same amount of people and roughly the same posting frequency, IMO you should've posted the question there. Don't know of any prior art but the approach doesn't seem unreasonable.

reefersleep 2024-03-20T11:53:13.927599Z

Thanks @p-himik 🙂 I was counting on you to reply 😄 I'll give #reagent a go next time I have a reagent-only answer. I did as I did this time based on my past experience with the channels, but maybe I've misperceived the difference in channel interaction.

p-himik 2024-03-20T11:54:35.260719Z

Probably the latter. :) And if I didn't answer at some point to your question in #reagent, I wouldn't answer it here either - because I probably didn't know the answer.

🫡 1
reefersleep 2024-03-20T11:56:00.995079Z

Is there a pattern for "pure" components, as I describe them, in re-frame? I haven't looked for a while, but the past code I've seen has had a lot of @globally-setup-subscription directly in components.

reefersleep 2024-03-20T11:57:57.783129Z

Specifically, I'm interested in the "pure" components so that I can showcase them in Portfolio (or any devcardish framework, really) without having to set up global state.

p-himik 2024-03-20T12:00:46.257329Z

Issue 137 holds all meaningful ideas, as far as I'm aware. tl;dr: there's no agreed upon pattern for pure or "pure" components. I myself use ad-hoc subs for truly global state (e.g. currently logged in user) and parameterized subs for everything else.

reefersleep 2024-03-20T12:47:54.466869Z

Thank you very much! I'll have a read-through for inspiration 🙂

reefersleep 2024-03-20T12:49:05.168119Z

It's a tough ask, really, There could be so many args that you'd want to pass into any given component in a reasonably complex/big app, and "arg complexity" is a counterweight to the allure of purity 🙂

reefersleep 2024-03-20T12:50:05.112959Z

My recent experiences with Portfolio have been a breath of fresh, pure air, so I'm interested in how deep that rabbit hole goes/how deep I can dig it 😄

p-himik 2024-03-20T12:53:01.114709Z

Somewhat of a middleground could be using a modified version of re-frame that passes the app-db ratom around. I think there's a fork that does that. But also, conceptually is not at all different from passing around some context that never or almost never changes. But it would change when you're using Portfolio.

p-himik 2024-03-20T12:55:35.594989Z

Such a context would be used for all components, or at least those that need some state. It can be anything, probably just some unique value under which all the state sits in the global ratom. So in the case of re-frame the global app-db, instead of containing something like {:user {...}, :panel :home, ...} would have {[:portfolio-page 0] {:user {...}, :panel :home, ...}}. That [:portfolio-page 0] is the value that you pass everywhere.

dehli 2024-03-20T17:53:53.845709Z

Hello 👋 I'm using a port of re-frame (refx) and I'm encountering some odd behavior and curious if it's a bug in the library or if re-frame also behaves similarly. I have a single event that's modifying 2 keys in the db (key "A" and key "B). I then have 2 subscriptions in the same component (one returns the value at "A" and the other "B"). The behavior I'm seeing is that on first render one of the subscriptions returns the updated value but the 2nd subscription returns the old value. Does re-frame also have similar behavior? Thanks!

p-himik 2024-03-20T18:01:28.346119Z

If what you describe is implemented in the simplest possible way, then no, re-frame doesn't behave like that. Both subs return the latest values. It is possible to make it behave like that, but you have to create conditional diamond structures in the subscription graph.

dehli 2024-03-20T18:04:58.490479Z

Thanks @p-himik! How could the subscription graph be conditional?

p-himik 2024-03-20T18:06:46.740429Z

Something like this:

(rf/reg-sub-raw :x
  (fn [db _]
    (if @(rf/subscribe [:a])
      @(rf/subscribe [:b])
      @(rf/subscribe [:c]))))

dehli 2024-03-20T18:10:02.893539Z

Ahhh, I see. That makes sense. I'm not doing anything like that so it makes me think there's a bug. Thanks! I will investigate 🕵️‍♂️

👍 1
Kimo 2024-03-21T20:10:51.159779Z

check out #refx if you haven't already

dehli 2024-03-21T20:11:14.014169Z

Oohh, didn't realize there was a dedicated channel for it! thanks @kimo741!

Kimo 2024-03-21T20:13:29.455509Z

@p-himik that's a very pernicious problem. Wish I could understand exactly why that happens & how to fix it. One thing I know for sure is that reactions don't exhibit the bug, only subscriptions.

dehli 2024-03-21T20:17:35.909879Z

I believe I discovered the issue (and a potential solution) with the problem I had posted. The issue was b/c use-hook under the hood uses useExternalStore and it was being notified right when a subscription was updated. This meant that when a sub would update, it would notify that there was a change, and then the render would happen. Then the next sub would update and it would notify again, resulting in another render. I was able to fix the bug using next-tick so that notifying the listeners doesn't happen until all subscription values are updated.

🙌 1
p-himik 2024-03-21T20:18:03.831939Z

@kimo741 Reactions do exhibit the bug.