Fork me on GitHub
#re-frame
<
2019-03-05
>
jokela11:03:10

So here's something that has been bothering me for a while now: Our team is working on a large cljs application which is based on re-frame and reagent. The subscription structure is heavily utilizing signal subscriptions which we have found to be a good decision as far as reusability, DRY-ness and simplicity is considered. However, we often run into the problem of requiring access to various parts of the state in the event handlers, with no simple way to access it. For example, I may have a subscription for :my-user/first-name, but no function capable of db -> my-first-name operation.

jokela11:03:57

Currently we're solving this by using inject-sub -coeffect from vimsical/re-frame-utils, which allows injection of subscriptions as coeffects for event handlers. However, we could also choose to not use subscription signals but instead write db->data functions and nest those. That would lose a part of the simplicity (and caching), however.

jokela11:03:04

I can't help feeling that re-frame is sort of split in two as far as best practices are considered. On one hand being able to use subscriptions as inputs for other subscriptions is great. On the other hand it makes writing event handlers more difficult. I'm also wondering if this is an issue that re-frame should eventually be able to solve by itself (without having to resort of inject-sub) , or if it already does but I'm just too blind to realize it.

valtteri11:03:52

@ipismai this questions pops up here every once in a while and AFAIK there’s no silver bullet. You pretty much described the alternative approaches above. Something to consider is why you need to do some-complicated-operation in both, event handlers and subs, in first place? Sometimes it’s a good idea to store the result of some-complicated-operation into the app-db. But that depends of course on the particular case at hand.

jokela13:03:11

@valtteri I think a representative example would be for some-complicated-operation to be the function build-user-entity which is related to a user-editor view. user-entity is built by merging together the existing data (retrieved through a subscription like [:user/entity-by-id <user-id>]) and the edits made (retrieved through a subscription that queries the app-db).

(rf/reg-sub ::edited-user-entity
  (fn [db]
    (let [{:keys [first-name last-name]} (get-in db [:path :to :editor :state])]
      (cond-> {}
              first-name (assoc :first-name first-name)
              last-name (assoc :last-name last-name)))))

(rf/reg-sub ::user-entity
  (fn [[user-id]]
    :<- [::user/entity user-id]
    :<- [::edited-user-entity])
  (fn [base-data edited-data]
    (merge base-data edited-data))) ; some-complicated-operation
The value returned by ::user-entity is then used to a) assert the validity of the data (used for UI visual feedback), b) as an input for the editor component (populating the input field values), and c) as the payload sent to the POST endpoint (injected via inject-sub). As far as I can tell, there is no way to accomplish storing the result of merging the two states without just moving the problem down another level (Having to use ::inject-sub when setting editor-state), or to completely stop using signal subscriptions.

valtteri14:03:54

@ipismai In similar case I start editing with something like this

(re-frame/reg-event-db
 ::set-user-to-edit
 (fn [db [_ {:keys [id]}]]
   (assoc-in db [:admin :editing-user] (get-in db [:admin :users id]))))
And then the editor modifies the map under [:admin :editing-user] directly. Subsequent events can pick it up from there and no some-complicated-operation is needed.

victorb16:03:31

Hello everyone! In re-frame, if I only need data for a specific page, where should I dispatch the event to start loading the data? Don't want it to always load, only when on a specific page, but I also don't want to start dealing with component/did-mount and hacks like that if I can avoid it

lilactown16:03:08

on navigation to the page?

valtteri16:03:15

Hi @victorbjelkholm429! I dispatch events when routing to the page.

valtteri16:03:55

For example Reitit routing lib has “controllers” exactly for this purpose https://metosin.github.io/reitit/frontend/controllers.html

victorb16:03:14

thanks! Yeah, so I would have to have some kind of API for dealing with that. Currently I have my own solution for routing

victorb16:03:33

thanks for the pointer to Reitit

valtteri16:03:51

I can warmly recommend it. There’s also #reitit

kwladyka18:03:54

(defn notifications []
  (let [messages (re-frame/subscribe [::notifications-subs/queue])]
    (fn []
      (into [:div
             {:style {:z-index 999
                      :position "fixed"
                      :bottom 0
                      :padding 20
                      :display "grid"
                      :grid-gap 20}}]
            (for [[id content] (reverse (take 2 @messages))
                  :let [open? (r/atom true)]]
              ;[(fn [])]
              [mui/snackbar
               {:open @open?
                :Transition-props {:direction "right"}
                :style {:position "static"}
                :anchor-origin {:vertical "bottom"
                                :horizontal "left"}
                :key id
                ;:auto-hide-duration 5000
                :on-close (fn [_ reason]
                            (when (= "timeout" reason)
                              (reset! open? false)
                              (println "close" @open?)
                              #_(re-frame/dispatch [::notifications-events/remove id])))
                :on-exited #(re-frame/dispatch [::notifications-events/remove id])
                }
               [:div
                [:p {:on-click #(reset! open? false)}
                 "@open? " (pr-str @open?)]
                content]])))))
I have an issue about for. How to make this code to show on screen correctly? The issue is mui/snackbar use transition, so when I use [fn () ...] to wrap [mui/snackbar ...] after each change of for result it makes animation in again. It should do animation out only for notification which go out. But it do animation out for all notification and notification in for all notifications when anything change. How to do it correctly?

kwladyka18:03:26

Sorry I described it so chaotic, but it made me so confuse

kwladyka18:03:57

I have queue in (re-frame/subscribe [::notifications-subs/queue]) and I want to show 2 notifications at once

kwladyka18:03:24

But the issue is I use transitions when they appear and disappear

kwladyka19:03:18

Hmm it is so strange. I have feeling when I change r/atom in second item. It change it at the same time in second and third (or first) one. Interesting. Like atom is changed for second item, but second item is recreated with different content, but reagent still think it is old item with old state.

kwladyka19:03:35

oh I see, it is why there is ^{:key id}, it fix the issue

kwladyka20:03:11

(defn notification []
  (let [open? (r/atom true)]
    (fn [[id content]]
      [mui/snackbar
       {:open @open?
        :Transition-props {:direction "right"
                           :timeout 2000}
        :style {:position "static"}
        :anchor-origin {:vertical "bottom"
                        :horizontal "left"}
        :key id
        ;:auto-hide-duration 3000
        :on-close (fn [_ reason]
                    (when (= "timeout" reason)
                      (reset! open? false)
                      (println "close" @open?)))
        :on-exited #(re-frame/dispatch [::notifications-events/remove id])
        }
       [:div {:on-click #(reset! open? false)}
        content]])))

(defn notifications []
  (let [messages (re-frame/subscribe [::notifications-subs/queue])]
    (fn []
      (into [:div
             {:style {:z-index 999
                      :position "fixed"
                      :bottom 0
                      :padding 20
                      :display "grid"
                      :grid-gap 20}}
             [:p (pr-str @messages)]]
            (for [msg @messages]
              ^{:key (first msg)} [notification msg])))))
^ solution