Skip to content
Mike Thompson edited this page Feb 22, 2016 · 49 revisions

1. Why Can't I access Subscriptions From Event Handlers?

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:

  1. A query function db -> val.
  2. 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. :-)

2. Can I Subscribe in A Subscription Handler?

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)))))

3. Why Can't I Call dispatch-sync In An Event Handler?

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 use dispatch-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:

  1. dispatch an event - it will be handled sometime AFTER the current handler completes.
  2. 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 of 5
  • no change :a - surprisingly it doesn't have the value 100

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:

  1. event handler for [:b] called with db snapshot
  2. event handler for [:a] called, with db snapshot
  3. event handler for [:a] returns modified db which is put into app-db
  4. 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.

3. Can re-frame use my logging functions?

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 ...})

4. How can I denormalise data within a re-frame application?

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.

5. How can I Inspect app-db?

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!

6. Dispatched Events Are Null

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 to an event handler, you must call (.persist event) before the dispatch. 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 you dispatch pure, simple cljs data (ie. rather than js objects) testing and debugging will become easier.

N. Can I Add An FAQ Entry?

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!!

Clone this wiki locally