2016-09-05 15:04:14 +10:00
|
|
|
## Debugging Event Handlers
|
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
This page describes techniques for debugging re-frame's event handlers.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
|
|
|
Event handlers are quite central to a re-frame app. Only event handlers
|
2016-12-17 01:09:30 -08:00
|
|
|
can update `app-db` to "step" an application "forward" from one state
|
2016-09-05 22:08:25 +10:00
|
|
|
to the next.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-11-07 09:12:24 +13:00
|
|
|
|
2016-09-05 15:04:14 +10:00
|
|
|
## The `debug` Interceptor
|
|
|
|
|
2016-10-19 21:19:02 +11:00
|
|
|
You might wonder: is my event handler making the right changes to `app-db`?
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
During development, the built-in `re-frame.core/debug` interceptor can help.
|
2016-10-19 21:19:02 +11:00
|
|
|
It writes to `console.log`:
|
2016-09-05 15:04:14 +10:00
|
|
|
1. the event being processed, for example: `[:attempt-world-record true]`
|
2016-09-07 15:40:52 +10:00
|
|
|
2. the changes made to `db` by the handler in processing the event
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
`debug` uses `clojure.data/diff` to compare the value of `app-db`
|
|
|
|
before and after the handler ran, showing what changed.
|
2016-09-05 22:08:25 +10:00
|
|
|
|
2016-10-19 21:19:02 +11:00
|
|
|
[clojure.data/diff returns a triple](https://clojuredocs.org/clojure.data/diff)
|
|
|
|
, the first two entries of which
|
2017-05-29 22:13:13 +10:00
|
|
|
`debug` will display in `console.log` (the 3rd says what has not changed and isn't interesting).
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
The output produced by `clojure.data/diff` can take some getting used to,
|
|
|
|
but you should stick with it -- your effort will be rewarded.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
### Using `debug`
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-10-19 21:19:02 +11:00
|
|
|
So, you will add this Interceptor like this:
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
2016-09-05 22:08:25 +10:00
|
|
|
(re-frame.core/reg-event-db
|
|
|
|
:some-id
|
2017-05-29 22:13:13 +10:00
|
|
|
[re-frame.core/debug] ;; <---- added here!
|
2016-09-05 22:08:25 +10:00
|
|
|
some-handler-fn)
|
2016-09-05 15:04:14 +10:00
|
|
|
```
|
|
|
|
|
2016-10-19 21:19:02 +11:00
|
|
|
Except, of course, we need to be more deft - we only want
|
|
|
|
`debug` in development builds. We don't
|
2016-09-07 15:40:52 +10:00
|
|
|
want the overhead of those `clojure.data/diff` calculations in production.
|
2016-10-19 21:19:02 +11:00
|
|
|
So, this is better:
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
2016-09-05 22:08:25 +10:00
|
|
|
(re-frame.core/reg-event-db
|
|
|
|
:some-id
|
2017-05-29 22:13:13 +10:00
|
|
|
[(when ^boolean goog.DEBUG re-frame.core/debug)] ;; <---- conditional!
|
2016-09-05 22:08:25 +10:00
|
|
|
some-handler-fn)
|
2016-09-05 15:04:14 +10:00
|
|
|
```
|
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
`goog.DEBUG` is a compile time constant provided by the `Google Closure Compiler`.
|
|
|
|
It will be `true` when the build within `project.clj` is `:optimization :none` and `false`
|
|
|
|
otherwise.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
Ha! I see a problem, you say. In production, that `when` is going to
|
2017-05-29 22:13:13 +10:00
|
|
|
leave a `nil` in the interceptor vector. So the Interceptor vector will be `[nil]`.
|
2016-09-07 15:40:52 +10:00
|
|
|
Surely that's a problem?
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Well, actually, no it isn't. re-frame filters out any `nil` from interceptor vectors.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
### Too Much Repetition - Part 1
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-07 15:40:52 +10:00
|
|
|
Each event handler has its own interceptor stack.
|
|
|
|
|
|
|
|
That might be all very flexible, but does that mean we have to put this `debug`
|
|
|
|
business on every single handler? That would be very repetitive.
|
|
|
|
|
|
|
|
Yes, you will have to put it on each handler. And, yes, that could be repetitive, unless
|
|
|
|
you take some steps.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-12-17 01:09:30 -08:00
|
|
|
One thing you can do is to define standard interceptors at the top of the `event.cljs` namespace:
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
2016-09-07 15:40:52 +10:00
|
|
|
(def standard-interceptors [(when ^boolean goog.DEBUG debug) another-interceptor])
|
|
|
|
```
|
|
|
|
|
|
|
|
And then, for any one event handler, the code would look like:
|
|
|
|
```clj
|
|
|
|
(re-frame.core/reg-event-db
|
|
|
|
:some-id
|
|
|
|
standard-interceptors ;; <--- use the common definition
|
|
|
|
some-handler-fn)
|
2016-09-05 15:04:14 +10:00
|
|
|
```
|
|
|
|
|
2016-09-07 15:40:52 +10:00
|
|
|
or perhaps:
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
2016-09-05 22:08:25 +10:00
|
|
|
(re-frame.core/reg-event-db
|
|
|
|
:some-id
|
2016-09-07 15:40:52 +10:00
|
|
|
[standard-interceptors specific-interceptor] ;; mix with something specific
|
2016-09-05 22:08:25 +10:00
|
|
|
some-handler-fn)
|
2016-09-05 15:04:14 +10:00
|
|
|
```
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
So that `specific-interceptor` could be something required for just this one
|
|
|
|
event handler, and it can be combined the standard ones.
|
2016-09-07 15:40:52 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Wait on! "I see a problem", you say. `standard-interceptors` is a `vector`, and it
|
|
|
|
is within another `vector` along side `specific-interceptor` - so that's
|
2016-09-05 22:08:25 +10:00
|
|
|
nested vectors of interceptors!
|
|
|
|
|
|
|
|
No problem, re-frame uses `flatten` to take out all the nesting - the
|
2017-05-29 22:13:13 +10:00
|
|
|
result is a simple chain of interceptors. And, as we have discussed,
|
|
|
|
`nil`s are removed.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
|
|
|
## 3. Checking DB Integrity
|
|
|
|
|
2016-09-07 15:40:52 +10:00
|
|
|
Always have a detailed schema for the data in `app-db`!
|
|
|
|
|
|
|
|
Why?
|
2016-09-05 22:08:25 +10:00
|
|
|
|
|
|
|
**First**, schemas serve as invaluable documentation. When I come to
|
|
|
|
a new app, the first thing I want to look at is the underlying
|
|
|
|
information model - the schema of the data. I hope it is well
|
|
|
|
commented and I expect it to be rigorous and complete, using
|
|
|
|
[Clojure spec](http://clojure.org/about/spec)
|
|
|
|
or, perhaps, [a Prismatic Schema](https://github.com/Prismatic/schema).
|
|
|
|
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
**Second** a good spec allows you to assert the integrity and correctness of
|
2016-09-07 15:40:52 +10:00
|
|
|
the data in `app-db`. Because all the data is in one place, that means you
|
2017-05-29 22:13:13 +10:00
|
|
|
are asserting the integrity of ALL the data in your app, at one time. All of it.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
When should we do this? Ideally, every time a change is made!
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Well, it turns out that only event handlers can change the value in
|
|
|
|
`app-db`, so only an event handler could corrupt it. So, we'd like to
|
|
|
|
**recheck the integrity of `app-db` immediately
|
2016-09-07 15:40:52 +10:00
|
|
|
after *every* event handler has run**.
|
2016-09-05 22:08:25 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
All of it, every time. This allows us to catch any errors very early,
|
|
|
|
easily assigning blame (to the rouge event handler).
|
2016-09-07 15:40:52 +10:00
|
|
|
|
|
|
|
Schemas are typically put into `db.cljs` (see the todomvc example in the re-frame repo). Here's
|
2017-05-29 22:13:13 +10:00
|
|
|
an example using Prismatic Schema
|
2016-09-05 22:08:25 +10:00
|
|
|
(although a more modern choice would be to use [Clojure spec](http://clojure.org/about/spec)):
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
|
|
|
(ns my.namespace.db
|
|
|
|
(:require
|
|
|
|
[schema.core :as s]))
|
|
|
|
|
|
|
|
;; As exactly as possible, describe the correct shape of app-db
|
|
|
|
;; Add a lot of helpful comments. This will be an important resource
|
|
|
|
;; for someone looking at you code for the first time.
|
|
|
|
(def schema
|
|
|
|
{:a {:b s/Str
|
|
|
|
:c s/Int}
|
|
|
|
:d [{:e s/Keyword
|
|
|
|
:f [s/Num]}]})
|
2016-09-07 15:40:52 +10:00
|
|
|
```
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-07 15:40:52 +10:00
|
|
|
And a function which will check a db value against that schema:
|
|
|
|
```clj
|
2016-09-05 15:04:14 +10:00
|
|
|
(defn valid-schema?
|
|
|
|
"validate the given db, writing any problems to console.error"
|
|
|
|
[db]
|
|
|
|
(let [res (s/check schema db)]
|
|
|
|
(if (some? res)
|
|
|
|
(.error js/console (str "schema problem: " res)))))
|
|
|
|
```
|
|
|
|
|
2016-09-07 15:40:52 +10:00
|
|
|
Now, let's organise for `valid-schema?` to be run **after** every handler.
|
|
|
|
We'll use the built-in `after` Interceptor factory function:
|
2016-09-05 15:04:14 +10:00
|
|
|
```clj
|
2016-09-05 22:08:25 +10:00
|
|
|
(def standard-interceptors [(when ^boolean goog.DEBUG debug)
|
2016-09-05 15:04:14 +10:00
|
|
|
(when ^boolean goog.DEBUG (after db/valid-schema?))]) ;; <-- new
|
|
|
|
```
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Now, the instant a handler messes up the structure of `app-db` you'll be alerted.
|
|
|
|
But this overhead won't be there in production.
|
2016-09-05 15:04:14 +10:00
|
|
|
|
2016-09-05 22:08:25 +10:00
|
|
|
### Too Much Repetition - Part 2
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Above, we discussed a way of "factoring out" common interceptors into `standard-interceptors`.
|
2016-09-07 15:40:52 +10:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
There's an additional technique we can use to ensure that all event handlers get certain Interceptors:
|
2016-09-07 15:40:52 +10:00
|
|
|
you write a custom registration function -- a replacement for `reg-event-db` -- like this:
|
2017-07-17 12:20:56 +02:00
|
|
|
```clj
|
2016-09-05 22:08:25 +10:00
|
|
|
(defn my-reg-event-db ;; alternative to reg-event-db
|
2017-07-17 12:20:56 +02:00
|
|
|
([id handler-fn]
|
2017-05-29 22:13:13 +10:00
|
|
|
(my-reg-event-db id standard-interceptors handler-fn))
|
2016-09-05 22:08:25 +10:00
|
|
|
([id interceptors handler-fn]
|
2017-07-17 12:20:56 +02:00
|
|
|
(re-frame.core/reg-event-db
|
2016-09-05 22:08:25 +10:00
|
|
|
id
|
2017-05-29 22:13:13 +10:00
|
|
|
[standard-interceptors interceptors]
|
2016-09-05 22:08:25 +10:00
|
|
|
handler-fn)))
|
|
|
|
```
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Notice how this registration function inserts our standard interceptors every time.
|
2016-09-07 15:40:52 +10:00
|
|
|
|
|
|
|
From now on, you can register your event handlers like this and know that the two standard Interceptors have been inserted:
|
2016-09-05 22:08:25 +10:00
|
|
|
```clj
|
|
|
|
(my-reg-event-db ;; <-- adds std interceptors automatically
|
|
|
|
:some-id
|
|
|
|
some-handler-fn)
|
|
|
|
```
|
2016-10-19 21:23:07 +11:00
|
|
|
|
|
|
|
### What about the -fx variation?
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
Above we created `my-reg-event-db` as a new registration function for `-db` handlers.
|
|
|
|
Now, `-db` handlers take `db` and `event` arguments, and return a new `db`.
|
|
|
|
So, they MUST return a new `db` value.
|
2016-10-19 21:23:07 +11:00
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
But what if we tried to do the same for `-fx` handlers which, instead, return
|
2016-12-17 01:09:30 -08:00
|
|
|
an `effects` map which may, or may not, contain a `:db`? Our solution would
|
2016-10-19 21:23:07 +11:00
|
|
|
have to allow for the absence of a new `db` value (by doing no validity check, because nothing
|
|
|
|
was being changed).
|
|
|
|
|
2017-05-29 22:13:13 +10:00
|
|
|
```clj
|
|
|
|
(def debug? ^boolean goog.DEBUG)
|
|
|
|
(def standard-interceptors-fx
|
2017-07-17 12:20:56 +02:00
|
|
|
[(when debug? debug) ;; as before
|
2017-05-29 22:13:13 +10:00
|
|
|
(when debug? (after #(if % (db/valid-schema? %)))]) ;; <-- different after
|
|
|
|
```
|
|
|
|
and then:
|
2017-07-17 12:20:56 +02:00
|
|
|
```clj
|
2016-10-19 21:23:07 +11:00
|
|
|
(defn my-reg-event-fx ;; alternative to reg-event-db
|
2017-07-17 12:20:56 +02:00
|
|
|
([id handler-fn]
|
|
|
|
(my-reg-event-fx id standard-interceptors-fx handler-fn))
|
2016-10-19 21:23:07 +11:00
|
|
|
([id interceptors handler-fn]
|
2017-07-17 12:20:56 +02:00
|
|
|
(re-frame.core/reg-event-fx
|
2016-10-19 21:23:07 +11:00
|
|
|
id
|
2017-07-17 12:20:56 +02:00
|
|
|
[standard-interceptors-fx interceptors]
|
2016-10-19 21:23:07 +11:00
|
|
|
handler-fn)))
|
|
|
|
```
|
|
|
|
|
2017-04-14 15:38:18 +10:00
|
|
|
|
|
|
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
|
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
|
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|