Last active
May 7, 2017 13:18
-
-
Save idibidiart/b454fe6f6b1e0f8f074e to your computer and use it in GitHub Desktop.
Dynamic Cursor for Reagent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns main.hello | |
(:require | |
[clojure.walk :as walk] | |
[reagent.core :as r])) | |
(enable-console-print!) | |
; Advantages of Dynamic Cursor vs Reagent's own Cursor-as-function: | |
; | |
; 1. Does not expose the bare-bone atom interface. So swap!, reset! assoc, assoc-in and @ are replaced by simple cGet/cSet methods | |
; | |
; 2. All cursors are created dynamically during the get operation and extend their root path with the sub-path you provide in | |
; the get operation such that updating the cursor of a deeply nested child won't cause Reagent to update (via React) components | |
; whose state dependencies are higher up in the path. This optimization typically involves | |
; having to manually define cursors of cursors and then use them in updating and dereferencing. This takes care of it dynamically | |
; at run time. | |
; | |
; 3. You can define in-transform and out-transform as part of each cursor and you can decide when to enable the transform | |
; | |
; 4. You can define multiple cursors in on cursor instance so you can pass a whole bunch of related cursors with one map | |
; | |
(defn fmap [f m] | |
(into (empty m) (for [[k v] m] [k (f v)]))) | |
; example state ratom | |
(def state-atom (r/atom {:parentA { | |
:child {:test1 8} | |
} | |
:parentB { | |
:child 111 | |
} | |
:parentC { | |
:child 211 | |
} | |
})) | |
; transforms | |
; | |
(defn double-it-map [m] | |
(fmap #(* % 2) m)) | |
(defn keywordize [string-map] | |
(walk/keywordize-keys string-map)) | |
;------------------------------------------------------------------------------------------ | |
; Dynamic Cursor v0.1 | |
; | |
; Interface | |
(defprotocol DynamicCursorInterface | |
(cGet [this c] [this c p]) | |
(cSet [this c v] [this c p v])) | |
; Class | |
(defrecord DynamicCursor [cursors] | |
DynamicCursorInterface | |
;; get | |
(cGet [this c] | |
(let [out-transform (or (:out-transform (c cursors)) identity)] | |
(if (keyword? c) | |
(let [cursor (r/cursor state-atom (:path (c cursors)))] | |
; apply out-transform | |
(try | |
(out-transform @cursor) | |
(catch js/Object e | |
(println (.-stack e)) | |
)) | |
) | |
(throw (.-stack (js/Error. | |
"invalid type: expected :cursor-id")))))) | |
(cGet [this c p] | |
(let [out-transform (or (:out-transform (c cursors)) identity)] | |
(if (and (keyword? c) (vector? p)) | |
(let [cursor (r/cursor state-atom (into (:path (c cursors)) p))] | |
; don't apply out-transform on nested path | |
@cursor) | |
(throw | |
(.-stack (js/Error. | |
"invalid types: expected :cursor-id and [:sub-path]")))))) | |
;; set | |
(cSet [this c v] | |
(let [in-transform (or (:in-transform (c cursors)) identity)] | |
(if (keyword? c) | |
; apply in-transform | |
(try | |
(swap! state-atom assoc-in (:path (c cursors)) (in-transform v)) | |
(catch js/Object e | |
(println (.-stack e)))) | |
(throw | |
(.-stack (js/Error. | |
"invalid types: expected :cursor-id and value")))))) | |
(cSet [this c p v] | |
(let [in-transform (or (:in-transform (c cursors)) identity)] | |
(if (and (keyword? c) (vector? p)) | |
; don't apply in-transform on nested path | |
(swap! state-atom assoc-in (into (:path (c cursors)) p) v) | |
(throw | |
(.-stack (js/Error. | |
"invalid types: expected :cursor-id [:sub-path] and value"))))))) | |
; Instance | |
(def abc-cursor (DynamicCursor. { | |
:my-test-cursor-1 { | |
:path [:parentA :child] | |
:in-transform keywordize | |
:out-transform double-it-map | |
} | |
:my-test-cursor-2 { | |
:path [:parentB :child] | |
:in-transform nil | |
:out-transform nil | |
} | |
} | |
)) | |
(println "Get value at my-test-cursor-2 where out-transform = nil: " | |
(cGet abc-cursor :my-test-cursor-2)) | |
(println "Get value at my-test-cursor-1 where out-transform = double-it-map: " | |
(cGet abc-cursor :my-test-cursor-1)) | |
(println "Set value at my-test-cursor-1 to string map {\"test\" 10} where in-transform = keywordize: " | |
(cSet abc-cursor :my-test-cursor-1 {"test" 10})) | |
; Console Output | |
; | |
;Get value at my-test-cursor-2 where out-transform = nil: 111 | |
;core.cljs:116Get value at my-test-cursor-1 where out-transform = double-it-map: {:test1 16} | |
;core.cljs:116Set value at my-test-cursor-1 to string map {"test" 10} where in-transform = keywordize: {:parentA {:child {:test 10}}, :parentB {:child 111}, :parentC {:child 211}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment