Skip to content

Instantly share code, notes, and snippets.

@the-frey
Created February 17, 2020 14:14
Show Gist options
  • Select an option

  • Save the-frey/0972ee645095728c3c214cb5b2623f00 to your computer and use it in GitHub Desktop.

Select an option

Save the-frey/0972ee645095728c3c214cb5b2623f00 to your computer and use it in GitHub Desktop.
Re-Frame Workshop Instructions

Re-Frame workshop

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.

Getting started

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

Task 1 - Fix subs

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

Task 2 - Fix events

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

Task 3 - New feature

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-db in db.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}}))

REPLs and s/emacs/$yr-editor/i

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.

Adding tests

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment