clojurescriptbulmareagentre-frame

Passing data between reagent components


I have the following reagent components

(defn comments-component [arg-id]
  [:p arg-id])

(defn arguments-component [list-of-args]
  [:ul.arguments
   (for [{:keys [comment upvotes id]} @list-of-args]
     ^{:key id}
     [:li
      [:p comment]
      [:p upvotes]
      [:p id
       [modal-button :argument
       ;title
        "Comments"
       ;body
        [:div
         [:p "Comments"]
         [comments-component id]]
        ;footer
       [:p "OpinionNeeded"] ]]])])
        

(defn debate []
  (let [debate-topic @(rf/subscribe [:debate/topic])
        debate-affirmatives (rf/subscribe [:debate/affirmatives])
        debate-negatives (rf/subscribe [:debate/negatives])]
    (fn []
      [:div
       [:a {:href "/"} "Return home"]
       [:p "This page is being worked on"]
       [:div
        [:h2 (get debate-topic :title)]
        [:p (get debate-topic :description)]]
       [:div.columns
        [:div.column.is-half
         [:p "Agree"]
         [arguments-component debate-affirmatives]]
        [:div.column.is-half
         [:p "Disagree"]
         [arguments-component debate-negatives]]]])))

The problem I'm encountering is that the modal button is supposed to create a modal popup with the id for each specific argument, (which I can then use to fetch the comments for that specific argument.)

But instead, I'm getting this bug whereby all the modals for different arguments show the same id.

I cannot figure out why this is happening, but it seems that all the modal-button functions are getting called with the id of the last or first argument to be rendered.

This is what the relevant portion of app-db looks like on this page

:affirmatives [{:id 1, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:negatives [{:id 2, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:comments [{:id 1, :comment "", :upvotes 0, :argument_id 1, :topic_id 2} {...}]

The :argument_id in each comment is a reference to the :id in the affirmatives/negatives

And here's the code that generates the modals.

(rf/reg-event-db
 :app/show-modal
 (fn [db [_ modal-id]]
  (assoc-in db [:app/active-modals modal-id] true)))

(rf/reg-event-db
 :app/hide-modal
 (fn [db [_ modal-id]]
  (update db :app/active-modals dissoc modal-id)))

(rf/reg-sub
 :app/active-modals
 (fn [db _]
  (:app/active-modals db {})))

(rf/reg-sub
 :app/modal-showing?
 :<- [:app/active-modals]
 (fn [modals [_ modal-id]]
  (get modals modal-id false)))

(defn modal-card [id title body footer]
 [:div.modal
  {:class (when @(rf/subscribe [:app/modal-showing? id]) "is-active")}
  [:div.modal-background
   {:on-click #(rf/dispatch [:app/hide-modal id])}]
  [:div.modal-card
   [:header.modal-card-head
    [:p.modal-card-title title]
    [:button.delete
     {:on-click #(rf/dispatch [:app/hide-modal id])}]]
   [:section.modal-card-body
    body]
   [:footer.modal-card-foot
    footer]]])

(defn modal-button [id title body footer]
 [:div
  [:button.button.is-primary
   {:on-click #(rf/dispatch [:app/show-modal id])}
   title]
  [modal-card id title body footer]])

Solution

  • All your modals are passed the same id :argument. Thus, you always show the same modal, since the db lookup in your subscription is for :argument.

    A minor tip: Following kebap-case is the recommended style in Clojure. So your app-db attributes should probably be argument-id and topic-id.

    Furthermore, I would recommend subscribing debate-affirmatives and debate-negatives inside the components you pass them to. This would make them more self-containing.