Using a Reagent atom within a Helix component

hi,

having used Reagent quite a bit, i’m now on a journey to get closer to React, and currently exploring Helix.

i have yet to learn how to reproduce the convenience of Reagent atoms in Helix. in the Helix docs, there is brief mention (here) that one could use a Reagent atom in a Helix component, but i do not know how (to write a custom hook, as the doc suggests).

hoping that this is rather trivial; anybody care to help me out?

1 Like

What are you trying to achieve? You can just pass the reagent atom into props.

With some 3rd party components I’ve occasionally had the issue that the component wouldn’t understand that the atom had changed. The workaround was to pass the dereferenced atom into the props, which I suspect can have occasional performance issue as the whole component gets recalculated.

But in general it’s seamless and works well to transition your app, I’m on the same journey atm.

hi Alex. what i’m trying to achieve is to be able to use an atom-like binding which, when it changes, triggers a React component re-render.

i know i can pass a dereferenced Reagent atom in the props (just like any value), and the changed props will trigger a re-render. but as far as passing a Reagent atom in the props, and expecting the view component to re-render if the Reagent atom changes—i can’t fathom how that could work (though i admit i haven’t tried it).

nice to know you’re on the same journey. i think, i’m happy i didn’t get a solution to this, because my initial intention was to see how it would be to avoid Reagent if i can (and use near-vanilla React via the Helix library).

The state of the art has gone through several changes since that helix doc was written, which was the big reason that I deferred publishing something at the time since how to correctly subscribe to external state was in flux. Now, it seems to have settled with the introduction of useSyncExternalStore in React 18 (and back ported to earlier versions).

An example I found of combining reagent and React hooks using useSyncExternalStore is here: https://github.com/roman01la/hooks/blob/main/src/hooks/reagent.cljs

I haven’t vetted the impl, but it looks similar to what I would write and @roman01la is also the author of UIx, a similar library to helix, so I have confidence that his solution would be a good place to start.

I think the only thing that might not be necessary in React 18 is the batched subscribe machinery, since the latest React 18 includes batched state setters built into it. I want to experiment without doing my own batching first, then if it doesn’t work I’d reuse what Roman’s library or something very similar.

2 Likes

As part of my subscriptions library which makes use or reagent I implemented a react hook to subscribe to reagent atoms, reactions, and cursors.

The entry namespace is here:

and the actual hook implementation is here (which was originally copied/inspired from the one linked above, but found it didn’t work):

This implementation memoizes the subscription vectors to prevent needless re-renders, as well as retaining every subscription a component uses for its lifetime so that those subscriptions are cached for the entire lifetime of the component.

A simple example usage is in the readme:

1 Like

@rmschindler There’s an up to date implementation of React hook that subscribes React components to any Atom-like data type. The code is in UIx repo, but there’s not much to do to adapt it to Helix or any other Hooks-based library uix/interop-with-reagent.md at master · pitch-io/uix · GitHub We’ve been using it for more than a year now at Pitch as interop layer between hooks and re-frame subscriptions, works fine thus far.

1 Like

Sorry to come back on an old thread. The solution from @roman01la looks like this:

(def counter (r/atom 0))

(defui title-bar []
  (let [n (use-reaction counter) ;; Reagent's reaction
        title (use-subscribe [:app/title])] ;; re-frame subscription
    ($ :div
      title
      ($ :button {:on-click #(swap! counter inc)}
        n))))

which is fine. I am instead using effects to update reagent atoms or the re-frame database. I’m just wondering whether my solution is as efficient? Particularly worried that the component gets redrawn twice as it’s also an input to the component, and not sure how to test. Example with a switch button:

(defn checked-atom (r/atom true))

(defnc switch
  "This is the typical switch at the top right of a box to display or hide it"
  [{:keys [checked-atom default-checked]}]
  (let [[checked setChecked] (use-state default-checked)
        x (use-effect [checked] (reset! checked-atom checked))]
    ($ Switch {:size "small" :checked checked :onChange (fn [e] (setChecked (aget e "target" "checked")))})))

Thank you!

I have this tidbit to add:

When you pass an atom (or a Reagent atom) as a prop to a React component, the atom’s dereferenced value is irrelevant—that is to say, when it changes, that does not count as an updated prop, and so will not cause a re-render.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.