Announcing https://github.com/ingesolvoll/glimt, 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 "<http://example.com/customer/123>"
:method :get
:response-format (ajax/json-response-format {:keywords? true})}
:max-retries 5
:path [::customers 123]})
This is mostly glue code around the excellent clj-statecharts by @wxitb2017, and heavily inspired by the thoughts outlined in https://github.com/day8/re-frame-http-fx-alpha. Will stay in snapshot until clj-statecharts is released
@ingesol you can also post to #announcements
@tugh Thanks!
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:
(re-frame/reg-event-db
::open-file
(fn [db [_ file]]
(let [reader (js/FileReader.)]
(set! (.-onload reader) #(re-frame/dispatch [::load-data (.. % -target -result)]))
(.readAsText reader file))
(assoc db :file file)))
(re-frame/reg-event-db
::load-data
(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?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
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 @p-himik. I will have a think and may have followup questions afterwards 👍
So this would be used within the ::open-file
event handler like this?:
(re-frame/reg-event-fx
::open-file
(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).
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
(:require
;; 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:
;; <https://github.com/facebook/react/issues/18178>
_ (._set-opts ^clj r {:auto-run true})]
r))
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]
@rf-sub))
: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))))