Fork me on GitHub
#re-frame
<
2024-03-20
>
reefersleep11:03:51

Hello guys I would post this in #C0620C0C8 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-himik11:03:23

Both #C0620C0C8 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.

reefersleep11:03:13

Thanks @U2FRKM4TW 🙂 I was counting on you to reply 😄 I'll give #C0620C0C8 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-himik11:03:35

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

1
reefersleep11:03:00

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.

reefersleep11:03:57

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-himik12:03:46

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.

reefersleep12:03:54

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

reefersleep12:03:05

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 🙂

reefersleep12:03:05

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-himik12:03:01

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-himik12:03:35

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.

dehli17:03:53

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-himik18:03:28

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.

dehli18:03:58

Thanks @U2FRKM4TW! How could the subscription graph be conditional?

p-himik18:03:46

Something like this:

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

dehli18:03:02

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 :male-detective:

👍 1
Kimo20:03:51

check out #C041XN6DADU if you haven't already

dehli20:03:14

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

Kimo20:03:29

@U2FRKM4TW 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.

dehli20:03:35

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-himik20:03:03

@U02J388JDEG Reactions do exhibit the bug.