Skip to content
H​eikki H​okkanen edited this page May 22, 2020 · 60 revisions

This document is OLD and contains outdated information. Instead, please see the modern FAQs within the repo

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.
(reg-event-db
  :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. 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:

(reg-event-db
  :a
  (fn [db _]
    (assoc db :a 100)))

(reg-event-db
  :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. 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.