-
-
Notifications
You must be signed in to change notification settings - Fork 716
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
reactionwrapping
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-syncif 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:
-
dispatchqueues the event for handling "later" -
dispatch-syncruns 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:
-
dispatchan event - it will be handled sometime AFTER the current handler completes. -
dispatch-syncan 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:
-
:bwith 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
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
dispatcha react js event object to an event handler, you must call(.persist event)before thedispatch. React recycles events (using a pool), and re-frame event handlers run async. More here - it is probably more idiomatic to extract the salient data from the event and
dispatchthat, rather than the js event object itself. When youdispatchpure, 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: