Skip to content

Instantly share code, notes, and snippets.

@dustingetz
Forked from idibidiart/dynamic-cursor
Last active January 29, 2016 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dustingetz/34aa3ac68b340bb471b3 to your computer and use it in GitHub Desktop.
Save dustingetz/34aa3ac68b340bb471b3 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}}
@dustingetz
Copy link
Author

updating the cursor of a deeply nested child won't cause Reagent to update components whose state dependencies are higher up

how does this work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment