NB: This repo is available at the short URL https://tinyurl.com/ll-feb-20-workshop
The goal of this workshop is to better understand how re-frame event flows work, and how to build and debug re-frame apps.
The basics of the framework are simple, and scale pretty well, so we'll be going over the basics of the entire re-frame event flow.
First, clone the repo here.
Then, run the shadow-cljs watcher for the first time. Shadow is a build tool that bridges CLJ and Node, allowing for more seamless interop between the two platforms.
$ npx shadow-cljs watch app
The first time, it will take a while to run.
When the build has finished, it will log the ports it is running on, and then something like:
[:app] Build completed. (512 files, 0 compiled, 0 warnings, 25.08s)
When that happens, you can navigate to port 8080 in your browser and open the app.
There's also a shorthand in the NPM runner:
$ npm run dev:watch
In order to get started, check out the workshop-task-1 branch. You'll find that things are now broken. Good news - we're going to fix them!
Open subs.cljs in src/cljs/todomvc and let's have a look.
You'll see lots of documentation - that's because this is a canonical re-frame example and is used as living docs in the main re-frame repo.
You'll notice that the :showing sub no longer works. It's referenced in the view code with:
(subscribe [:showing])subscribe takes as its argument a vector, where the first item is a unique keyword that references the sub, then any additional arguments.
The sub is relatively simple - it's just extracting the value of the top-level :showing key from the db.
We're going to implement it on line 15.
(reg-sub
:showing
(fn [db _] ;; <-- additional arguments would go in a vector in the _ position
(:showing db)))For the next task, we're going to check out the workshop-task-2 branch. Again, you'll find things are broken. Again, we're coming to the rescue!
Open events.cljs in src/cljs/todomvc and let's have a look.
Again you'll see lots of docs. You might also notice after looking that there's no :set-showing event in the namespace.
This is the way that it's dispatched in the view. dispatch takes a vector, that like for a subscription, takes a unique keyword as its first item, followed by any other arguments.
(dispatch [:set-showing :active])We're going to implement the missing :set-showing event.
Note that the vector passed to dispatch is symmetrical with that in the matching reg-event-db.
(reg-event-db
:set-showing
(fn [db [_ new-filter-kw]]
(assoc db :showing new-filter-kw)))Also, there is an interceptor that checks the spec of the input. Interceptors are passed as a vector of functions.
(reg-event-db
:set-showing
[check-spec-interceptor]
(fn [db [_ new-filter-kw]]
(assoc db :showing new-filter-kw)))Okay, this is a somewhat contrived feature, but we're going to add a character counter.
Every time the textbox value changes, we'll update a counter in the app-db, then reveal that value on screen.
We'll use an event called :update-text-edits, which will track the number of total changes to the textbox.
Steps:
- Implement an event,
:update-text-edits. - Make a place for the data in the
app-dbindb.cljs - Implement a sub,
:total-text-edits - Subscribe to the sub and show the data in the view
In some cases, using events like this for something that changes at a high frequency could result in big performance issues, particularly on mobile.
For more information, looking at Reagent (the React templating library used under the hood) and form-3 components is useful.
You can also debounce effects. This would be achieved by something like:
(ns todomvc.debounce
(:require [re-frame.core :refer [reg-fx dispatch]]
[schema.core :as s])) ;; this ns uses schema to validate inputs etc
(defn now [] (.getTime (js/Date.)))
(def registered-keys (atom nil))
(def DebouncedEventSchema
{:key s/Keyword
:event [s/Any]
:delay s/Num})
(defn dispatch-if-not-superceded [{:keys [key delay event time-received]}]
(when (= time-received (get @registered-keys key))
;; no new events on this key!
(dispatch event)))
(defn dispatch-later [{:keys [delay] :as debounce}]
(js/setTimeout
(fn [] (dispatch-if-not-superceded debounce))
delay))
;; works in a similar fashion to the lodash debounce fn
(reg-fx
:dispatch-debounce
(fn dispatch-debounce [debounce]
(try
(s/validate DebouncedEventSchema debounce)
(catch js/Object e
(error e)))
(let [ts (now)]
(swap! registered-keys assoc (:key debounce) ts)
(dispatch-later (assoc debounce :time-received ts)))))You could then use it like so:
;; now you call this event instead of the original one
(reg-event-fx
:debounced-update-text-edits
(fn [fx [_ text]]
{:dispatch-debounce {:key :update-text-edits ;; unique key
:event [:update-text-edits text] ;; the original event
:delay 250}}))Your best bet is to read the shadow-cljs docs here. The CLJS REPL is a lot more usable than it was even a year or two ago.
Some scaffolding for tests has been added via Karma. You'll need to install it via NPM to get cracking though.
$ npm install -g karma-cli
Then you will be able to run:
$ lein karma