Lade...
 

Combine re-frame 0.8 with datascript and posh

mathiasp Samstag November 12, 2016

The past: no obvious way


For some time I wanted to use re-frame, the framework/pattern for writing in-browser apps using reagent and react, and combine this with datascript, the in-browser datalog database modelled after datomic.

But re-frame is build around holding all data inside an atom, and I found no good way to combine both. Also, the authors of re-frame said that datascript was to slow in their experience. So maybe it was a bad idea after all...

re-frame 0.8, posh, a new order


Lastly two things happened that made me reconsider:

With the new release 0.8.0 re-frame introduced something new: Effects and Co-Effects, designed to keep re-frame event functions pure. And with these co/effects handler, combining re-frame with datascript seemed like a simple task to me.

And on the performance side posh popped up: a library for writing datascript-based apps which sped up datascript use within UIs considerably.

So I tried again to combine re-frame and datascript using effectfull handlers from re-frame and posh for performance, and it turned out to be easy. Next step will be to find out how this performs.

Idea

The basic idea is to put (only) a datascript connection in re-frames db and from then on only use effectfull handlers to update the datascript database, instead of updating the re-frame central app-db atom.
Subscriptons largely work as before, using the reg-sub-raw instead of the new reg-sub, because posh returns reactions allready.

Implementation

The minimal changes to the re-frame-template I found look like this:

First, add posh to project.clj

project.clj
(defproject re-posh "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.9.229"]
                 [reagent "0.6.0"]
                 [re-frame "0.8.0"]
                 [posh "0.5.4"]]


Now lets add posh and datascript declarations to db.cljs

src/cljs/re-posh/db.cljs
(ns re-posh.db
  (:require [posh.reagent :refer [pull q posh!]]
            [datascript.core :as d]))


and put a datascript db into the re-frame atom

src/cljs/re-posh/db.cljs
(def default-db
  {:db (d/create-conn)})


Let's activate posh for this db and put some data in it:

src/cljs/re-posh/db.cljs
(posh! (:db default-db))

(d/transact! (:db default-db)
             [{:db/id 1
               :posh/name "re-posh"}])


Now that we have our datascript database, let's modify the subscription to use it:

First add posh and datascript (yes, I know, boring):

src/cljs/re-posh/subs.cljs
(ns re-posh.subs
  (:require-macros [reagent.ratom :refer [reaction]])
  (:require [re-frame.core :as re-frame]
            ;; add posh + datascript
            [posh.reagent :refer [pull q posh!]]
            [datascript.core :as d]))


Now use reg-sub-raw, so our posh result (a reaction) doesn't get wrapped into another reaction

src/cljs/re-posh/subs.cljs
(re-frame/reg-sub-raw
 :name
 (fn [db nix]
   (pull (:db @db) '[:posh/name] 1)))


Easy, right?

But since posh/datascript pull syntax returns maps we'll need to slightly change our view by using {:posh/name @result} instead of just @result.

src/cljs/re-posh/views.cljs
(defn main-panel []
  (let [result (re-frame/subscribe [:name])]
    (fn []
      ;; get the name from the pull result
      [:div "Hello from " (:posh/name @result)])))


And now to the interesting stuff: use re-frame's new effectful handler to run
datascript transactions. For this we need an effect handler who actually does the transaction, and then we just use that in an reg-event-fx handler to change the database.

Again, include posh and datascript:

src/cljs/re-posh/events.cljs
(ns re-posh.events
  (:require [re-frame.core :as re-frame]
            [re-posh.db :as db]
            [posh.reagent :refer [pull q posh!]]
            [datascript.core :as d]))


The initialize-db handler stays the same, so I don't show it here, we only add a new effect handler to run datascript transactions for us.

src/cljs/re-posh/events.cljs
;; the new event handler, just running the transaction
(re-frame/reg-fx
 :transact!
 (fn [transaction]
   (d/transact! (:db @re-frame.db/app-db)
                transaction)))


And now the effectful handler. It doesn't even need to return the re-frame db, it just returns the datascript transaction.
Remember, a datascript transactions is only data

src/cljs/re-posh/events.cljs
(re-frame/reg-event-fx
 :change-name
 (fn [cofx [_ name]]
   {:transact! [{:db/id 1
                 :posh/name name}]}))


Compile this with lein figwheel dev and use {re-frame.core/dispatch :change-name %22Gobblydok%22) to see the effect in the website.

I've yet to play with debugging this, but I think it show's the power of re-frame's new effectful handlers.