Tianxiang Xiong edited this page Jan 22, 2018 · 59 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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.