ingesol 2020-11-16T08:21:44.325200Z

Announcing, a simple FSM wrapper around the :http-xhrio effect with retries and error handling. State of request is tracked through a simple subscription instead of callback/dispatch. Code example:

(def fsm {:id          :customer-loader
          :http-xhrio  {:uri             "<>"
                        :method          :get
                        :response-format (ajax/json-response-format {:keywords? true})}
          :max-retries 5
          :path        [::customers 123]})

ingesol 2020-11-16T08:22:45.325400Z

This is mostly glue code around the excellent clj-statecharts by @wxitb2017, and heavily inspired by the thoughts outlined in Will stay in snapshot until clj-statecharts is released

tugh 2020-11-16T11:49:38.326300Z

@ingesol you can also post to #announcements

ingesol 2020-11-16T13:54:46.326500Z

@tugh Thanks!

paulbutcher 2020-11-16T17:21:45.332Z

I have a re-frame style question. I’m writing a data analysis application (for racecar telemetry, as it happens) which means that I need to open and parse CSV files. Currently I’m doing this in two steps, an ::open-file event (fired when the user selects a file) and a ::load-data event which does the parsing. This is what they currently look like:

 (fn [db [_ file]]
   (let [reader (js/FileReader.)]
     (set! (.-onload reader) #(re-frame/dispatch [::load-data (.. % -target -result)]))
     (.readAsText reader file))
   (assoc db :file file)))

 (fn [db [_ data]]
   (assoc db :data (parse data))))
I don’t particularly like the fact that I’m creating the FileReader within the event handler, but I’m not sure which of the various different choices that are open to me would make sense. Should I: • Do this in an interceptor (via the after interceptor factory)? • Do this in another event handler (dispatched by the ::open-file handler)? • Get over myself because it’s not too bad as it stands? • Something else?

dpsutton 2020-11-16T17:24:37.332800Z

love the profile pic and intent of the project. To my mind mutable state needs to be local. keeping the reader inside of a single let is by far the best solution to me.

paulbutcher 2020-11-16T17:26:30.333200Z

Thank you. “Get over myself” it is 👍😊

dpsutton 2020-11-16T17:27:20.333600Z

many times just being aware that there's a question here means you're gonna do it right regardless of what the answer is

p-himik 2020-11-16T17:40:35.333800Z

It should be put in an effect, just like all side effects.

p-himik 2020-11-16T17:41:42.334Z

Here's how I do it:

(defn read-file [file callback]
  (doto (js/FileReader.)
    (oset! :onload #(callback (oget file :name) (oget % :target.result)))
    (ocall :readAsText file)))

(reg-fx :read-file
  (fn [params]
    (let [{:keys [file on-read]} params]
      (read-file file (fn [_ content]
                        (dispatch (conj on-read content)))))))

p-himik 2020-11-16T17:42:26.334200Z

oget is just a function from cljs-oops that gets the passed JS property from the passed object. So (oget file :name) is the same as (.-name ^js file).

p-himik 2020-11-16T17:42:42.334400Z

Same for oset! and ocall.

paulbutcher 2020-11-16T18:21:51.334600Z

Thanks @p-himik. I will have a think and may have followup questions afterwards 👍

paulbutcher 2020-11-16T18:53:43.334800Z

So this would be used within the ::open-file event handler like this?:

  (fn [db [_ file]]
    {:db (assoc db :file file)
     :read-file [file :load-data]}))

p-himik 2020-11-16T19:40:33.335Z

More like :read-file {:file file, :on-read [:load-data]}.

paulbutcher 2020-11-16T20:57:28.335300Z

Thanks. And thanks for the introduction to cljs-oops, which will save me from lots of typing ^js and getting annoyed when I discover that my production build is suddenly broken).


Could someone please review this code for me? I'm integrating re-frame subs with react hooks. This code is working in my application, but I'm not certain that setting :auto-run the way I am is a good idea, or whether I'm cleaning up properly.

(ns app.hooks
   ;; A lib provided by react to make these types of integrations easier:
   ["use-subscription" :as react.use-subscription]
   [re-frame.interop :as rf.interop]
   [re-frame.core :as rf]
   [helix.hooks :as h.hooks] ; cljs wrapper of the react hooks api

(defn- maybe-dispose! [^clj reaction]
  (when-not (seq (.-watches reaction))
    (rf.interop/dispose! reaction)))

(let [n (atom 0)] ;; incremented int used to get unique keys for add-watch.
  (defn use-sub [query]
    (let [rf-sub (h.hooks/use-memo [query]
                                   (let [r (rf/subscribe query)
                                         ;; If the reaction isn't set to autorun,
                                         ;; watches won't fire when the reaction is "dirty".
                                         ;; Reactions get "dirty" when their inputs change.
                                         ;; Setting :auto-run also seems to avoid an
                                         ;; issue where derefs
                                         ;; trigger setState calls in /other/ components
                                         ;; that subscribe to the same query.
                                         ;; React warns about this:
                                         ;;  <>
                                         _ (._set-opts ^clj r {:auto-run true})]
          sub (h.hooks/use-memo [rf-sub]
                                #js{:getCurrentValue (fn []
                                                       ;; Get rid of any reactive context
                                                       ;; because we don't want
                                                       ;; tracking of this deref.
                                                       (binding [ratom/*ratom-context* nil]
                                    :subscribe (fn [callback]
                                                 (let [k (str "use-sub-" (swap! n inc))]
                                                   (add-watch rf-sub k callback)
                                                   (fn []
                                                     (remove-watch rf-sub k)
                                                     (maybe-dispose! rf-sub))))})]
      (react.use-subscription/useSubscription sub))))