re-frame still adds way too much boilerplate for my tastes, especially on solo projects. I personally greatly prefer to use reagent directly over using re-frame.
; One Macro in events.clj:
(defmacro go-loop-sub [pub key binding & body]
(let [[db-bind & bindings] binding]
`(let [ch# (cljs.core.async/chan)]
(cljs.core.async/sub ~pub ~key ch#)
(go-loop []
(let [event# (rest (cljs.core.async/<! ch#))
~db-bind @yourproject.data.core/conn
~(vec bindings) event#
result# (do ~@body)]
(when result#
(yourproject.data.core/transact! yourproject.data.core/conn result#)))))
(recur)))))
;; Usage (at top of core file)
(defonce conn (d/create-conn schema))
(defonce event-bus (async/chan))
(defonce event-pub (async/pub event-bus first))
;; For each event you want to dispatch on:
(go-loop-sub event-pub :todo/add-item [db {:as data :keys [text]}]
;; Query db here. Any tx-data you return will get transacted.
[{:db/id -1
:todo/text text}])
;; To display some reactive queries in DataScript:
(rum/defc root-component
< rum/reactive [conn]
(let [db (rum/react conn)
todos (d/q '[:find ?todo ?text
:where [?todo :todo/text ?text]]
db)]
[:div "Todos: " (pr-str todos)]))
That's it. You don't need re-frame.It's not perfect, I do lament the boilerplateyness myself, but I have not seen anything better in my decade of web dev.
I think using re-frame rather than just building event loops yourself with channels and atoms give you some advantage in terms of existing libraries and utilities.
Usually I end up wrapping things myself so I have one "setup-handler :name" function/macro that creates the subscription and event db handler for me. There are also existing libraries that does this for you, if writing it yourself is not wanted.
One example of such library: https://gitlab.com/nikperic/reframe-utils
`reg-basic-sub` has this outcome:
(reg-basic-sub :common/active-page)
;; Equivalent to
(reg-basic-sub :common/active-page :common/active-page)
;; Equivalent to
(reg-sub
:common/active-page
(fn [db _]
(:common/active-page db)))