(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}}