Skip to content

Commit d287376

Browse files
dannyfreemandannyfreeman
authored andcommitted
Allow subscribe to be safely called from any context
The `warn-when-not-reactive` warnings are no longer valid if we bypass the subscription cache when not in a reagent component, so they've been removed. This should address issue #753 This change probably requires some documentation cleanup
1 parent 69cf395 commit d287376

File tree

2 files changed

+64
-47
lines changed

2 files changed

+64
-47
lines changed

src/re_frame/subs.cljc

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,29 @@
3535
(defn cache-and-return
3636
"cache the reaction r"
3737
[query-v dynv r]
38-
(let [cache-key [query-v dynv]]
39-
;; when this reaction is no longer being used, remove it from the cache
40-
(add-on-dispose! r #(trace/with-trace {:operation (first-in-vector query-v)
41-
:op-type :sub/dispose
42-
:tags {:query-v query-v
43-
:reaction (reagent-id r)}}
44-
(swap! query->reaction
45-
(fn [query-cache]
46-
(if (and (contains? query-cache cache-key) (identical? r (get query-cache cache-key)))
47-
(dissoc query-cache cache-key)
48-
query-cache)))))
49-
;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
50-
(swap! query->reaction (fn [query-cache]
51-
(when debug-enabled?
52-
(when (contains? query-cache cache-key)
53-
(console :warn "re-frame: Adding a new subscription to the cache while there is an existing subscription in the cache" cache-key)))
54-
(assoc query-cache cache-key r)))
55-
(trace/merge-trace! {:tags {:reaction (reagent-id r)}})
56-
r)) ;; return the actual reaction
38+
;; Only cache the reaction when subscribe is called from a reactive context
39+
;; where a reagent component will be able to purge the reaction from the subscription cache
40+
;; when the component is dismounted.
41+
(when (reactive?)
42+
(let [cache-key [query-v dynv]]
43+
;; when this reaction is no longer being used, remove it from the cache
44+
(add-on-dispose! r #(trace/with-trace {:operation (first-in-vector query-v)
45+
:op-type :sub/dispose
46+
:tags {:query-v query-v
47+
:reaction (reagent-id r)}}
48+
(swap! query->reaction
49+
(fn [query-cache]
50+
(if (and (contains? query-cache cache-key) (identical? r (get query-cache cache-key)))
51+
(dissoc query-cache cache-key)
52+
query-cache)))))
53+
;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
54+
(swap! query->reaction (fn [query-cache]
55+
(when debug-enabled?
56+
(when (contains? query-cache cache-key)
57+
(console :warn "re-frame: Adding a new subscription to the cache while there is an existing subscription in the cache" cache-key)))
58+
(assoc query-cache cache-key r)))
59+
(trace/merge-trace! {:tags {:reaction (reagent-id r)}})))
60+
r) ;; Always return the actual reaction, even if it was not cached.
5761

5862
(defn cache-lookup
5963
([query-v]
@@ -63,17 +67,8 @@
6367

6468
;; -- subscribe ---------------------------------------------------------------
6569

66-
(defn warn-when-not-reactive
67-
[]
68-
(when (and debug-enabled? (not (reactive?)))
69-
(console :warn
70-
"re-frame: Subscribe was called outside of a reactive context.\n"
71-
"See: https://day8.github.io/re-frame/FAQs/UseASubscriptionInAJsEvent/\n"
72-
"https://day8.github.io/re-frame/FAQs/UseASubscriptionInAnEventHandler/")))
73-
7470
(defn subscribe
7571
([query]
76-
(warn-when-not-reactive)
7772
(trace/with-trace {:operation (first-in-vector query)
7873
:op-type :sub/create
7974
:tags {:query-v query}}
@@ -92,7 +87,6 @@
9287
(cache-and-return query [] (handler-fn app-db query)))))))
9388

9489
([query dynv]
95-
(warn-when-not-reactive)
9690
(trace/with-trace {:operation (first-in-vector query)
9791
:op-type :sub/create
9892
:tags {:query-v query

test/re_frame/subs_test.cljs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[reagent.ratom :as r :refer-macros [reaction]]
44
[re-frame.subs :as subs]
55
[re-frame.db :as db]
6+
[re-frame.interop]
67
[re-frame.core :as re-frame]))
78

89
(test/use-fixtures :each {:before (fn [] (subs/clear-all-handlers!))})
@@ -87,16 +88,37 @@
8788
(swap! side-effect-atom inc)
8889
(reaction @db)))
8990

90-
(let [test-sub (subs/subscribe [:side-effecting-handler])]
91-
(reset! db/app-db :test)
92-
(is (= :test @test-sub))
93-
(is (= @side-effect-atom 1))
94-
(subs/subscribe [:side-effecting-handler]) ;; this should be handled by cache
95-
(is (= @side-effect-atom 1))
96-
(subs/subscribe [:side-effecting-handler :a]) ;; should fire again because of the param
97-
(is (= @side-effect-atom 2))
98-
(subs/subscribe [:side-effecting-handler :a]) ;; this should be handled by cache
99-
(is (= @side-effect-atom 2))))
91+
(testing "Inside of a reactive context"
92+
(with-redefs [re-frame.interop/reactive? (constantly true)]
93+
(let [test-sub (subs/subscribe [:side-effecting-handler])]
94+
(reset! db/app-db :test)
95+
(is (= :test @test-sub))
96+
(is (= @side-effect-atom 1))
97+
(subs/subscribe [:side-effecting-handler]) ;; this should be handled by cache
98+
(is (= @side-effect-atom 1))
99+
(subs/subscribe [:side-effecting-handler :a]) ;; should fire again because of the param
100+
(is (= @side-effect-atom 2))
101+
(subs/subscribe [:side-effecting-handler :a]) ;; this should be handled by cache
102+
(is (= @side-effect-atom 2)))))
103+
104+
(subs/clear-subscription-cache!)
105+
(reset! side-effect-atom 0)
106+
107+
(testing "Outside a reactive context"
108+
(let [test-sub (subs/subscribe [:side-effecting-handler])]
109+
(is (= :test @test-sub))
110+
(is (= @side-effect-atom 1)
111+
"Subscribed once")
112+
(subs/subscribe [:side-effecting-handler])
113+
(is (= @side-effect-atom 2)
114+
"Subscribed twice and the cache was never populated.")
115+
(with-redefs [re-frame.interop/reactive? (constantly true)]
116+
(subs/subscribe [:side-effecting-handler :a])
117+
(is (= @side-effect-atom 3)
118+
"Fires again because of the param, but this time the cache is populated"))
119+
(subs/subscribe [:side-effecting-handler :a])
120+
(is (= @side-effect-atom 3)
121+
"The cache was already populated, so the cached reaction is used."))))
100122

101123
;============== test clear-subscription-cache! ================
102124

@@ -105,14 +127,15 @@
105127
:clear-subscription-cache!
106128
(fn clear-subs-cache [db _] 1))
107129

108-
(testing "cold cache"
109-
(is (nil? (subs/cache-lookup [:clear-subscription-cache!]))))
110-
(testing "cache miss"
111-
(is (= 1 @(subs/subscribe [:clear-subscription-cache!])))
112-
(is (some? (subs/cache-lookup [:clear-subscription-cache!]))))
113-
(testing "clearing"
114-
(subs/clear-subscription-cache!)
115-
(is (nil? (subs/cache-lookup [:clear-subscription-cache!])))))
130+
(with-redefs [re-frame.interop/reactive? (constantly true)]
131+
(testing "cold cache"
132+
(is (nil? (subs/cache-lookup [:clear-subscription-cache!]))))
133+
(testing "cache miss"
134+
(is (= 1 @(subs/subscribe [:clear-subscription-cache!])))
135+
(is (some? (subs/cache-lookup [:clear-subscription-cache!]))))
136+
(testing "clearing"
137+
(subs/clear-subscription-cache!)
138+
(is (nil? (subs/cache-lookup [:clear-subscription-cache!]))))))
116139

117140
;============== test register-pure macros ================
118141

0 commit comments

Comments
 (0)