Created
May 6, 2020 09:56
-
-
Save orestis/546923dd508d7de3e629fef35ad26066 to your computer and use it in GitHub Desktop.
This file contains 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 hooks-store | |
(:require ["react" :as react] | |
["react-dom" :as react-dom])) | |
(defprotocol IStore | |
(-trigger-subs [this old-state new-state]) | |
(-get-value [this selector]) | |
(destroy [this]) | |
(subscribe [this selector on-change]) | |
(unsubscribe [this k])) | |
(deftype Store [backing subs watch-key] | |
IStore | |
(destroy [this] | |
(reset! subs {}) | |
(remove-watch backing watch-key)) | |
(-trigger-subs [this old-state new-state] | |
(doseq [[selector on-change] (vals @subs)] | |
(let [oldv (selector old-state) | |
newv (selector new-state)] | |
(when-not (= oldv newv) | |
(on-change newv))))) | |
(-get-value [this selector] | |
(selector @backing)) | |
(subscribe [this selector on-change] | |
(let [k (random-uuid)] | |
(swap! subs assoc k [selector on-change]) | |
k)) | |
(unsubscribe [this k] | |
(swap! subs dissoc k) | |
k)) | |
;; borrowed from hx | |
(defn useValue | |
"Caches `x`. When a new `x` is passed in, returns new `x` only if it is | |
not structurally equal to the previous `x`. | |
Useful for optimizing `<-effect` et. al. when you have two values that might | |
be structurally equal by referentially different." | |
[x] | |
(let [-x (react/useRef x)] | |
;; if they are equal, return the prev one to ensure ref equality | |
(let [x' (if (= x (.-current -x)) | |
(.-current -x) | |
x)] | |
;; Set the ref to be the last value that was succesfully used to render | |
(react/useEffect (fn [] | |
(set! (.-current -x) x) | |
js/undefined) | |
#js [x']) | |
x'))) | |
(defn useStore [store selector] | |
;; aim here is to subscribe to the store on mount, | |
;; unsubscribe on unmount, and always return the | |
;; current value of the store | |
(let [init-v (-get-value store selector) | |
[init-v setState] (react/useState init-v) | |
selector' (useValue selector)] | |
(react/useEffect | |
(fn [] | |
(let [k (subscribe store selector setState)] | |
(fn unsub [] | |
(unsubscribe store k)))) | |
#js [store selector']) | |
;; return the current value | |
init-v)) | |
(defn new-store [backing] | |
(let [watch-key (random-uuid) | |
store (Store. backing (atom {}) watch-key) | |
watch-fn (fn [_ _ old-state new-state] | |
(-trigger-subs store old-state new-state))] | |
(add-watch backing watch-key watch-fn) | |
store)) | |
(defonce backing-store (atom {})) | |
(defonce store (new-store backing-store)) | |
(comment | |
(reset! backing-store {:counter 0}) | |
(swap! backing-store update :counter2 inc) | |
(-get-value store :counter) | |
@backing-store | |
(def k | |
(subscribe store identity (fn [v] (println "GOT NEW VALUE: " v)))) | |
(destroy store) | |
(keyword-identical? :counter :counter) | |
(start) | |
) | |
(defn Counter [props] | |
(println "Counter props" props) | |
(react/createElement "div" nil "Counter is: " (.-counter props))) | |
(defn Button [] | |
(react/createElement "button" #js {:onClick (fn [e] | |
(swap! backing-store update :counter inc))} | |
"Increase")) | |
(defn StatefulCounter [] | |
(let [counter (useStore store :counter)] | |
(react/createElement "div" nil | |
(react/createElement Counter #js {:counter counter}) | |
(react/createElement Button)))) | |
;; start is called by init and after code reloading finishes | |
(defn ^:dev/after-load start [] | |
(js/console.log "start") | |
(react-dom/render | |
(react/createElement StatefulCounter nil nil) | |
(js/document.getElementById "app")) | |
) | |
(defn ^:export init [] | |
;; init is called ONCE when the page loads | |
;; this is called in the index.html and must be exported | |
;; so it is available even in :advanced release builds | |
(js/console.log "init") | |
(start)) | |
;; this is called before any code is reloaded | |
(defn ^:dev/before-load stop [] | |
(js/console.log "stop")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment