re-frame workshop

A gentle introduction to

Daniel Janus
re:Clojure, 2020-12-04

How we’ll work

What we’ll build

Video © Joho345, (cc) BY-SA 4.0, source: Wikimedia Commons

re-frame

Building blocks of a re-frame app

Views: Hiccup notation

Representing HTML documents as Clojure data
            
[:div.content
  [:p "First paragraph"]
  [:img {:src "image.png"}]]

Yields:

            
<div class="content">
  <p>A paragraph of text</p>
  <img src="image.png"/>
</div>

Components: functions producing Hiccup

Data in, Hiccup out
            
(defn hello [{:keys [who color]}]
  [:p {:style {:color color}}
   "Hello, " who "!"])

(defn main []
  [:div
   [hello {:who "World", :color "green"}]
   [hello {:who "re:Clojure", :color "red"}]])

Exercise 1

  1. Clone the repo: github.com/nathell/solitaire
    • Or do git pull
  2. In the repo main directory, run lein watch
  3. Go to http://localhost:8280
  4. Edit the file src/cljs/solitaire/views.cljs
  5. Make the hello component parameter accept a :bold? key, rendering the greeting in bold if it’s true

Game board: solitaire/board.cljs

initial-board:


[[:blocked :blocked :peg :peg   :peg :blocked :blocked]
 [:blocked :blocked :peg :peg   :peg :blocked :blocked]
 [:peg     :peg     :peg :peg   :peg :peg     :peg]
 [:peg     :peg     :peg :empty :peg :peg     :peg]
 [:peg     :peg     :peg :peg   :peg :peg     :peg]
 [:blocked :blocked :peg :peg   :peg :blocked :blocked]
 [:blocked :blocked :peg :peg   :peg :blocked :blocked]]

Exercise 2

  1. Copy exercises/ex_02/views.cljs over the original views.cljs
  2. You’ll find already defined components that render each type of a field
  3. Your task is to change board-view to render all the fields of the board
  4. Use the comments in the file for guidance

Application state: app-db


(def default-db
  {:board board/initial-board})

Accessing state: subscriptions


(rf/reg-sub
 ::field          ; <- subscription name
 (fn [db [_ x y]] ; <- second arg: “query vector”
   {:x x
    :y y
    :type (get-in db [:board y x])}))

Using subscriptions in components


(defn board-view []
  (let [[width height] @(rf/subscribe [::subs/board-dimensions])]
    (into
     [:div.board]
     (for [y (range height)
           x (range width)]
       [field-view @(rf/subscribe [::subs/field x y])]))))

Dependent subscriptions


(rf/reg-sub
 ::board
 (fn [db _]
   (:board db)))

(rf/reg-sub
 ::board-dimensions
 :<- [::board] ; instead of the entire db
 (fn [board _]
   (board/dimensions board)))

Selecting fields


(def default-db
  {:board board/initial-board
   :selected-field [3 1]})

Exercise 3

  1. Copy files in exercises/ex_03 over existing files
  2. Edit solitaire/subs.clj
  3. In the ::field subscription, add a new key, :selected?, telling us whether the peg is selected (highlighted)
  4. Use the comments in the file for guidance

Events


(rf/reg-event-db
 ::start
 (fn [db event-vector]
   (assoc db :board board/initial-board)))

Dispatching events


(defn field-peg [{:keys [selected? x y]}]
  [:div.field
   [:div.peg
    {:class (when selected? "peg--selected")
     :on-click #(rf/dispatch [::events/select-field x y])}]])

Exercise 4

  1. Copy files in exercises/ex_04 over existing files
  2. Edit solitaire/events.clj
  3. In the ::select-field subscription, add code to change :selected-field in db as necessary
  4. Use the comments in the file for guidance

Exercise 5

  1. Copy files in exercises/ex_05 over existing files
  2. Edit solitaire/views.clj
  3. In the field-empty component, add code to dispatch the ::events/make-move event on click
  4. Use the comments in the file for guidance

Effectful events


(rf/reg-event-fx
 ::end-game
 (fn [{db :db} _]
   {:db (assoc db :status :game-over)
    :alert "Game over!"}))

Ending and restarting game


(def default-db
  {:board          board/initial-board
   :selected-field nil
   :status         :not-started})

Copy files from exercises/final to see it in action

Do’s and Don’ts

Where to go from here

Questions?

Thank you!

tinyurl.com/re-frame-workshop-survey

Daniel Janus
@nathell