Fork me on GitHub
#re-frame
<
2023-03-11
>
Dameon16:03:50

Hello, I’ve been working with reframe for a little while, but I think I misunderstand something fundamental. I’ve tried reading the docs a few times but still don’t see what is wrong. I have a component with a pair of subscriptions. Then a dispatch to fetch data from an API. When that API completes successfully it saves the items to the DB, and that triggers the subscription. It works fine if that key in the DB isn’t set, but if it is set then it will only render what was previously set, even though the subscription triggers. However, the next time the page loads it will render what was set on the second request.

Get A -> Fetch A -> Show A
Get B -> Fetch B -> Show A
Get C -> Fetch C -> Show B

Dameon16:03:40

(rf/reg-event-fx
 :get-lot-items
 (fn [{db :db} [_ lot-id]]
   {:http-xhrio {:method :get
                 :uri    (str API_URL lot-id)
                 :timeout 5000
                 :with-credentials true
                 :response-format (ajax/transit-response-format {:handlers transit-date/readers :keywords? true})
                 :on-success [:success-get-lot-items]
                 :on-failure [:error-get-lot-items]}
    :db db}))

(rf/reg-sub
  :fetched-lot-items
  (fn [db _]
    (js/console.log "Setting Lot Items")
    (:lot-items db)))

(rf/reg-event-db
 :success-get-lot-items
 (fn [db [a b]]
   (js/console.log a)
   (js/console.log b)
   (-> db
    (assoc :lot-items (:result b)))))

(defn lot-items-page []
  (let [current-route @(rf/subscribe [::routes-subs/current-route])
        lot-id (-> current-route :path-params :lot-id)
        items @(rf/subscribe [:fetched-lot-items])]
    (rf/dispatch [:get-lot-items lot-id])
    [:div.container
     [:h1.title.is-1.block "Items For Lot"]
     (if (empty? items)
       [:h3.title.is-3.block "No Items Currently Available"]
       [paginator items item-card :div.columns.is-multiline.is-8])]))

Dameon16:03:17

The log “Setting Lot Items” tiggers correctly when navigating to B. And the app-db looks correct, the components just don’t re-render.

p-himik16:03:36

The only thing that looks wrong in your code is that you're calling dispatch as a side-effect of rendering. But that by itself shouldn't be an issue here. If you create a minimal reproducible example, I'll take a look.

Dameon16:03:07

Let me see what I can do.

Dameon17:03:34

I haven’t been able to reproduce it just yet, but it has something to do with the fact that I’m using that paginator function. When I just dump out the items in raw json it reloads fine. In another page where I was having a similar problem I had a template for a form that I passed a subscribed item to. It had the same behavior where it wouldn’t reload properly. It seems like the subscription/template reloading isn’t propagating to sub components (not sure if they technically are components at that level.)

p-himik17:03:08

What's the code of paginator?

Dameon17:03:37

(ns client.components.paginator
  (:require
    [reagent.core :as r]))

(defn- cap-dec [cap] (fn [x] (if (>= cap x) cap (dec x))))

(defn- cap-inc [cap] (fn [x] (if (= cap x) x (inc x))))

(defn- cap- [cap] (fn [x n] (if (>= cap (- x n)) cap (- x n))))

(defn- cap+ [cap] (fn [x n] (if (<= cap (+ x n)) cap (+ x n))))

(defn- get-page-range [curr-page end-page num-pages]
  (cond
    (= num-pages 1) []
    (= num-pages 2) []
    (= num-pages 3) [2]
    (= curr-page 1) [2 3]
    (= curr-page end-page) (range (- end-page 2) end-page)
    :else (range ((cap- 2) curr-page 1) ((cap+ end-page) curr-page 2))))

(defn paginator [collection component container]
  (let [curr-page (r/atom 1)]
    (fn []
      (let [pages (partition-all 32 collection)
            num-pages (count pages)
            pages-from-start (- @curr-page 1)
            pages-from-end (- num-pages @curr-page)]
        [:div
          [container
           (for [el (nth pages (- @curr-page 1))]
            ^{:key el}
            [component el])]
          [:nav.pagination.is-centered {:role "navigation" :aria-label "pagination"}
           [:div.pagination-previous {:on-click #(swap! curr-page (cap-dec 1))
                                      :class (when (= 1 @curr-page) "is-disabled")} "Previous"]
           [:div.pagination-next {:on-click #(swap! curr-page (cap-inc num-pages))
                                  :class (when (= num-pages @curr-page) "is-disabled")} "Next"]

           [:ul.pagination-list
            [:li [:a.pagination-link {:on-click #(reset! curr-page 1)
                                      :aria-label "Goto Page 1"} 1]]

            (when (<= 3 pages-from-start) [:li [:span.pagination-ellipsis "..."]])

            (for [i (get-page-range @curr-page num-pages num-pages)]
              ^{:key (str "page" i)}
              [:li [:a.pagination-link {:on-click #(reset! curr-page i)
                                        :aria-label (str "Goto Page " i)} i]])

            (when (<= 3 pages-from-end) [:li [:span.pagination-ellipsis "..."]])

            (when (< 1 num-pages)
              [:li [:a.pagination-link {:on-click #(reset! curr-page num-pages)
                                        :aria-label (str "Goto Page " num-pages)} num-pages]])]]]))))

Dameon17:03:56

The form was built using this library https://github.com/luciodale/fork

Dameon17:03:26

The hack I had for the form was to use the will-unmount event to just clear the app db of the data. But seems like a bad practice to keep doing that.

Dameon17:03:09

I’ll give it a look thanks 🙂

Dameon21:03:25

@U2FRKM4TW I just got around to testing it, that worked! Thanks! I was certain it was a re-frame issue and didn’t even think of looking at the reagent docs.

👍 2
p-himik21:03:05

Re-frame is really just a thin library on top of Reagent. It doesn't even abstract away anything, it just provides some nice affordances.

Dameon21:03:20

Would I need to look in the reagent docs to see the proper place to put that dispatch?

Dameon21:03:40

I’ll keep that in mind when looking into problems. Thanks.

p-himik21:03:45

That would be re-frame docs. The gist is that views don't have side-effects in their render function, and you should perform all actions in other actions. E.g. that particular view of yours became visible thanks to some event - then that event should be the one that also fetches the data. Not everybody agrees with that, of course. But at the very least, you should be using form-3 components with :component-did-mount and :component-did-update or React hooks. An alternative is to use global interceptors and monitor some specific flag change - the flag that makes that view visible. When it's changed false->true, load the data, something like that.