- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 719
FAQ
Question
Those pesky re-frame "rules" say I can only use subscriptions in Components. But, in my event handlers, I need to use the same query. Why can't I use a subscription there too?
I am the Che Guevara of the Reagent world, and I will not be oppressed by their filthy capitalist, pig dog rules!!!
Answer
You should think about a subscription handler as having two parts:
- A query function
db -> val
. - a
reaction
wrapping
The reaction
wrapping delivers "a stream" of updates over time. The means the query will be rerun whenever "app-db" changes, and that's perfect for Components which, in response, might need to rerender
But event handlers don't need that. They need to do a single query, which yields one result, all based off the db
param supplied. They don't need a stream of query results.
So, if you find yourself needing to query db
in your event handlers, and wishing you could use a subscription, you should:
- factor out the reusable query into a function
- within your subscription, use that function, and wrap it in a reaction (to get a stream of values)
- within your event handlers, call the function directly (to get a single value).
Sketch:
(defn my-query
[db v]
.....) ;; return some interesting value based on db
;; a subscription handler
;; needs to produce a "stream" of changes, based on my-query
(register-sub
:some-id
(fn [app-db v]
(reaction (my-query @app-db v)))) ;; use my-query with @app-dp, in reaction
;; an event handler
;; needs to perform the query once, to obtain a value.
(register-handler
:h-id
(fn [db v]
(let [calc (my-query db v)] ;; use my-query to get a one off value
.... use calc)))
So now my-query
is available for use by event handlers, free of the reaction
wrapping.
And, yes, come the revolution, I'm sure we'll be the first ones against the wall. :-)
Question
I'd like to call one subscription in another subscription handler. Is that okay? If so, how should I do it?
Answer
Yes, it is fine to do that. Here's an example:
(register-sub
:data
(fn [db _] (reaction (:data @db))))
(register-sub
:sort-order
(fn [db _] (reaction (:sort-order @db))))
(register-sub
:filter
(fn [db _] (reaction (:filter @db))))
(register-sub
:table-data
(fn [db _]
(let [data (subscribe [:data]) ;; <--- subscribe used here
type-filter (subscribe [:filter]) ;; <--- and here
sort-order (subscribe [:sort-order])] ;; <--- and here
;; the final returned reaction uses returns from the subscribe calls above
;; remember to deref the returned values
(reaction (sort-by @sort-order (filter #(= (:type %) @filter) @data)))))
Note: in the above, the subscribe
calls are not themselves inside a reaction
. Rather the return of these calls is used in the final reaction. The following version is the wrong way to do it::
(register-sub
:table-data
(fn [db _]
(reaction ;; <--- reaction wraps calls to subscribe - IS WRONG
(let [data (subscribe [:data])
type-filter (subscribe [:filter])
sort-order (subscribe [:sort-order])]
(sort-by @sort-order (filter #(= (:type %) @filter) @data)))))
Question
In an event handler, I'm allowed to dispatch
further events. But I'm not allowed to use dispatch-sync
. Why? Aren't they pretty much the same?
Answer
As a general rule, you should always use
dispatch
. Only usedispatch-sync
if you specifically need it but, as this FAQ explains, never try to in an event handler.
dispatch
and dispatch-sync
are identical in intent, but they differ in terms of when the event's handler is run:
-
dispatch
queues the event for handling "later" -
dispatch-sync
runs the associated event handler RIGHT NOW.
This "later" vs "right now" difference is the key.
If we are currently halfway through running one event handler, and we:
-
dispatch
an event - it will be handled sometime AFTER the current handler completes. -
dispatch-sync
an event - it will be handled immediately, before the current handler completes.
To illustrate, assume we have these two simple event handlers:
(register-handler
:a
(fn [db _]
(assoc db :a 100)))
(register-handler
:b
(fn [db _]
(dispatch-sync [:a]) ;; <-- dispatch-sync used here
(assoc db :b 5)))
If we were to:
(dispatch [:b])
and then, afterwards, inspect app-db
we'd see:
-
:b
with a value of5
- no change
:a
- surprisingly it doesn't have the value100
It is as if (dispatch-sync [:a])
never happened. Its modification to :a
is lost.
Here's why. Because dispatch-sync
is used, the process is:
- event handler for [:b] called with db snapshot
- event handler for [:a] called, with db snapshot
- event handler for [:a] returns modified db which is put into
app-db
- event handler for [:b] returns modified db which is put into
app-db
Step 4 overwrites step 3, which means that step 2 is lost.
re-frame detects nested handler calls and will produce a warning if it occurs. This FAQ entry is here mostly to explain why you got that error.
Question
I use logging technique X, how do I make re-frame use my method?
Answer
re-frame makes use of the logging functions: warn
, log
, error
, group
and groupEnd
.
By default, these functions map directly to the js/console implementations, but you can override that by providing your own set or subset of these functions.
Use re-frame.core/set-loggers!
like this:
(defn my-warn
[text] ;; text is a string
.... I'll warn about 'text' in here)
(defn my-log
[text]
....)
(set-loggers! {:warn my-warn :log my-log ...})
Question
My app-db
is structured like a normalised database and want to "join" parts for display purposes. For example, I have many wibblies and I want to display them in a table, but each wibble has a wobble. How should I do that?
Answer
One way is to use a form-2 component which accepts an id and subscribes to a denormalising subscription based on that id:
(defn my-component[id]
(let [denormalised-state (subscribe [:denormaliser id])]
(fn [id]
[:div (:some-denormalised-state @denormalised-state)])))
See Colin Yates' exploratory repo here for more info.
Question
How can I inspect the contents of app-db
? Maybe from figwheel.
Answer
The short answer is to inspect: re-frame.db/app-db
The longer answer is: "are you sure you need to?". First, you seldom want to inspect all of app-db
. And, second, inspecting via figwheel will be clumsy.
Instead, you probably want to inspect a part of app-db
. And you probably want to inspect it in the GUI itself.
Here is a useful technique from @escherize. Add something like this to the hiccup of your view ...
[:pre (with-out-str (pprint @interesting))]
This assumes that @interesting is the value (ratom or subscription) you want to observe (note the @ in front).
pprint
output is nice to read, but not compact. For a more compact view, do this:
[:pre (pr-str @some-atom)] ;; using pr-str instead of pprint
If you choose to use pprint
then you'll need to require
it in at the top of your view.cljs:
[cljs.pprint :refer [pprint]]
@yogthos' excellent json-html library has an even slicker presentation (at the expense of more screen real estate, and the need to include specific CSS).
Finally, combining the short and long answers, you could even do this:
[:pre (with-out-str (pprint @re-frame.db/app-db))] ;; see everything!
or
[:pre (with-out-str (pprint (:part @re-frame.db/app-db)))] ;; see a part of it!
Question
If I dispatch
a js event object (from a view), it is nullified by the time it gets to the event-handler. What gives?
Answer
So there's two things to say about this:
- if you want to
dispatch
a react js event object in an event handler, you must call(.persist event)
before thedispatch
. React recycles events (using a pool), and re-frame event handlers run async. - it is probably more idiomatic to extract the salient data from the event and
dispatch
that, rather than the js event object itself. When youdispatch
pure, simple cljs data (ie. rather than js objects) testing and debugging will become easier.
Question
I'd like to add an FAQ question and answer. Can I do that?
Answer
Yes, please put one here if you think it useful! Then open an issue to get it reviewed. Many Thanks!!
Deprecated Tutorials:
Reagent: