Fork me on GitHub

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]})

👀 3

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


@U21QNFC5C you can also post to #announcements


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?


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


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)))))))


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).


Same for oset! and ocall.


Thanks @U2FRKM4TW. I will have a think and may have followup questions afterwards 👍


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]}))


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


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).


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.


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


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

👍 3

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))))