-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathtrack.cljc
123 lines (87 loc) · 3.21 KB
/
track.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
(ns vimsical.re-frame.fx.track
"Dispatch events when subscriptions change.
Tracking a subscription is a stateful process that needs to be registered and
disposed of using an id.
Fx input map:
`:action` either `:register` or `:dispose`
`:id` any value
`:subscription` a subscription vector
`:event-fn` a fn of a subscription value to an event vector.
Once a track for a subscription is registered, every time that subscription
updates the track will invoke `:event-fn` with the subscription's value, it
should return an event vector for `re-frame.core/dispatch` or nil for a no-op.
Usage:
(require '[vimsical.re-frame.fx.track :as track])
0. Context
Given the following subscription:
(re-frame/reg-sub ::my-sub (fn [_ sub-arg] ...))
We want to invoke a handler for every update
(re-frame/reg-event ::my-sub-did-update (fn [_ sub-arg sub-value] ...))
1. Register a track
(re-frame/reg-event-fx
::register-track
(fn [cofx event]
{::track/register
{:id :my-track
:subscription [::my-sub \"my-sub-arg\"]
:event-fn (fn [sub-value] [::my-sub-did-update \"my-sub-arg\" sub-value])}}))
2. Dispose of the track using its id
(re-frame/reg-event-fx
::dispose-track
(fn [cofx event]
{::track/dispose
{:id :my-track}}))
"
(:require
[re-frame.core :as re-frame]
[reagent.ratom :as ratom]))
;;
;; * Register
;;
(defonce ^:private register (atom {}))
;;
;; * Internal helpers
;;
(defn- new-reagent-track
"Create a new reagent track that will execute every time `subscription`
updates.
`event-fn` is invoked with the subscription value, it should return an event
vector for `re-frame.core/dispatch`, or nil for a no-op.
If `dispatch-first?` is true, dereference and dispatch right away using the
current value, if false dispatch will happen on the next update. Defaults to
true.
"
[{:keys [subscription event-fn dispatch-first?] :or {dispatch-first? true}}]
{:pre [(vector? subscription) (ifn? event-fn)]}
#?(:cljs
(let [dispatched-first? (atom false)]
(ratom/track!
(fn []
(let [sub-value @(re-frame/subscribe subscription)]
(when-some [event-vector (event-fn sub-value)]
(when (or dispatch-first?
@dispatched-first?
(do (reset! dispatched-first? true) nil))
(re-frame/dispatch event-vector)))))))))
(defn ensure-vec [x] (if (sequential? x) x [x]))
;;
;; * Fx handlers
;;
(defn register-fx
[track-or-tracks]
(doseq [{:keys [id] :as track} (ensure-vec track-or-tracks)]
(if-some [track' (get @register id)]
(throw (ex-info "Track already exists" {:track track' :tried track}))
(let [track (new-reagent-track track)]
(swap! register assoc id track)))))
(defn dispose-fx
[track-or-tracks]
(doseq [{:keys [id] :as track} (ensure-vec track-or-tracks)]
(if-some [track (get @register id)]
#?(:cljs (do (ratom/dispose! track) (swap! register dissoc id)) :clj nil)
(throw (ex-info "Track isn't registered" {:track track})))))
;;
;; * Entry point
;;
(re-frame/reg-fx ::register register-fx)
(re-frame/reg-fx ::dispose dispose-fx)