Skip to content

Instantly share code, notes, and snippets.

@idibidiart
Last active May 7, 2017 13:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save idibidiart/b454fe6f6b1e0f8f074e to your computer and use it in GitHub Desktop.
Save idibidiart/b454fe6f6b1e0f8f074e to your computer and use it in GitHub Desktop.
Dynamic Cursor for Reagent
(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