This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
- # announcements (1)
- # architecture (14)
- # asami (21)
- # babashka (1)
- # beginners (44)
- # biff (6)
- # calva (24)
- # clojure (16)
- # clojure-europe (12)
- # clojurescript (32)
- # cursive (23)
- # datascript (5)
- # honeysql (8)
- # hyperfiddle (1)
- # malli (1)
- # nextjournal (34)
- # nrepl (4)
- # off-topic (64)
- # re-frame (12)
- # reagent (1)
- # releases (2)
- # reveal (41)
- # shadow-cljs (137)
- # spacemacs (4)
- # xtdb (5)
I am attempting to build a reusable re-frame component containing re-com components:
The initializer in make-radio-button group looks like this:
(defn radio-button-group "Build a radio button group with the given id having cheices as labels/values and the given wrapper (one of h-box or v-box)." [choices wrapper id] [wrapper :src (rc/at) :size "none" :gap "20px" :children (mapv (fn [choice] (vector radio-button :src (rc/at) :label choice :value choice :model @(rf/subscribe [:radio-button-group-state id]) :on-change #(rf/dispatch [:on-radio-button-group-change id %]))) choices)]) (defn make-radio-button-group [choices wrapper] (let [rb-grp-id (gensym) _ (rf/dispatch-sync [:initialize-radio-button-group (first choices) rb-grp-id])] (radio-button-group choices wrapper rb-grp-id))) (defn container  (make-radio-button-group ["A" "B" "C"] h-box))
(which would be a lot simpler without the debugging stuff). In addition, I have a change handler for the radio button groups:
(rf/reg-event-db :initialize-radio-button-group (fn [db [_ init id]] (js/alert (str "Initializing radio button group " id " to " init)) (let [btn-state (get-in db [:radio-button-group-state id])] (js/alert "Current button group state is " btn-state) (let [new-db (when (nil? btn-state) (assoc-in db [:radio-button-group-state id] (r/atom init)))] (js/alert (str "Current db state is " new-db)) new-db))))
as well as a subscription function to the state of the radio button groups:
(rf/reg-event-db :on-radio-button-group-change (fn [db [_ id new-value]] (swap! (get-in db [:radio-button-group-state id]) (fn [_] new-value)) (js/alert (str "Now db value is " db)) db))
When I render the radio button group for the first time, everything works fine - the initial state is set to the first element of the choices vector and the button group displays fine. However, when I select a radio button in the group, the value changes, and the button group needs to re-render, which creates a new button group. The new button group sets its initial state, which re-creates and re-renders a new button group, and so it goes... Is there any way to stop re-frame from re-rendering/creating a new radio button group and have it keep the original one?
(rf/reg-sub :radio-button-group-state (fn [db [_ id]] @(get-in db [:radio-button-group-state id])))
Issues in your code:
• Mostly a cosmetic one - don't use
(vector ...), instead just use the vector literal,
• Don't call
gensym on each render
• Don't use
dispatch-sync in view functions
• Don't use
() for Reagent components - use
 instead, the difference is documented somewhere
• Don't store atoms in app-db - store the values themselves. There must be no
@ in your subscription/event handlers
Agreed with all of your recommendations and I'll fix the code, but as far as the second goes, where can I put the gensym so it gets called before the button group is rendered, but so it doesn't get re-invoked when something higher up the render tree gets re-rendered (other than shoving it all the way up into the main function and then passing it down through the intermediate render functions)? Basically, I want the id to be created when the group is created, but later I don't want to re-create the group each time the container is re-rendered.
Rendering should be a side-effect of your data change - not the other way around. In other words, the fact of something being rendered should not affect anything. There are exceptions, of course (e.g. when you interact with a complex JS component), but this is not such a case.
Things are rendered because of data changes - so use
gensym or anything like that in things that change the data, namely in event handlers or things that feed them data, like effects or JS event handlers (`:on-click` for example).
Thanks for the tips. They made the code shorter, plus it has the advantage of working now. Thanks again.
I was wrong - things are not quite working as I had thought. Here's my new code:
I know you said to move the gensym into places for data changes. However, initially, I want to render container and the radio-button-group with the id which the gensym provides needs to be created before that. There's not a data change that triggers that - just a workflow. Or maybe I wasn't understanding what you said.
(defn radio-button-group "Build a radio button group with the given id having cheices as labels/values and the given wrapper (one of h-box or v-box)." [choices wrapper id] [wrapper :src (rc/at) :size "none" :gap "20px" :children (mapv (fn [choice] [radio-button :src (rc/at) :label choice :value choice :model @(rf/subscribe [:radio-button-group-state id]) :on-change #(do (js/alert %)(rf/dispatch [:on-radio-button-group-change id %]))]) choices)]) (defn container  (js/alert "Rendering container") (let [rbg-id (gensym)] [v-box :children [[radio-button-group ["A" "B" "C"] h-box rbg-id] [labeled-component "Radio button group value" [:div @(rf/subscribe [:radio-button-group-state rbg-id])]]]]))
You can put that
(gensym) some place where you initialize re-frame's app-db and grab the ID from there.
Alternatively, you can switch from a form-1 component to a form-2 one or to
reagent.core/with-let to keep the ID stable between re-renderers. But it won't save you if
container is not merely re-rendered but re-mounted, so I'd advice against that approach unless you absolutely certain about what you're doing and why.
I turned the form-1 into a form-2 component. I had forgotten about them. It's now working as I expected. Thanks again for your help.
Any suggestions for how to debug the following?
Clearly I have an event handler that’s setting
re-frame: ":fx" effect expects a seq, but was given null
:fxto null. But how do I find out which one? The call stack isn’t any help, sadly.
Yeah, the callstack will show re-frame's internals.
You can put a conditional breakpoint in your browser right where events are scheduled, in those very internals.
Alternatively, you can register a temporary global interceptor that would check for
That makes sense. Thanks 👍