-
-
Notifications
You must be signed in to change notification settings - Fork 715
Debugging
This is secret work in progress. It is still a bit fresh.
This page describes a simple and effective technique for debugging re-frame apps. It proposes a particular combination of tools. By the end, you'll be better at dominos.
re-frame apps are event driven.
Event driven apps have this core, perpetual loop:
- your app is in some quiescent state
- an event arrives (user presses a button, a websocket gets data, etc)
- computation/processing follows as the event is handled, leading to changes in app state, the UI, etc
- the app goes back into a quiescent state, patiently waiting for the next event.
When debugging an event driven system, our focus will be step 3.
With re-frame, step 3 happens in a specific way:
- 3.1. a
(dispatch [:event-id ....])happens (that's how events are initiated) - 3.2. an
Event Handleris run (along with middleware), changing the value inapp-db. - 3.3. one or more
subscriptionsfire (because of 3.2) - 3.4.
componentsrerender (because of 3.3)
The processing of every single event has that same timeline.
It is like a four domino sequence: an event arrives and Bang, Bang, Bang, one domino triggers the next. A delightfully regular environment to understand and debug!
Bret Victor has explained to us the importance of observability. In which case, when we are debugging re-frame, what do we want to observe?
re-frame's four domino process involves data values flowing in and out of relatively simple, pure functions. Derived data flowing. So, to debug we want to observe:
- which functions are called
- what data flowed in and out of them
Functions and data: What data was in the event? What event handler was then called? What middleware then ran? What state changes did that event handler cause? What subscription handlers were then triggered? What new values did they then return? And which Reagent components then rerendered? What hiccup did they return? It's all just functions processing data.
So, in Clojurescript, how do we observe functions and data? Well, as luck would have it, ClojureScript is a lisp and it is readily traceable.
I suggest a particular combination of technologies which, working together, will write a trace to the devtools console. Sorry, but there's no fancy SVG dashboard. We said simple, right?
First, use clairvoyant to trace function calls and data flow. We've had a couple of Clairvoyant PRs accepted, and they make it work well for us. We've also written a specific Clairvoyant tracer tuned for our re-frame needs. https://clojars.org/day8/re-frame-tracer.
Second, use cljs-devtools because it allows you to inspect traced data. That means you'll need to be using a very fresh version of Chrome. But it is worth it.
Finally, because we want you to easily scan, parse and drill into trace data, we'll be using Chrome devtool's console.group() and console.endGroup().
You'll needto install clj-devtools by following these instructions. You'll have to switch to Chrome Canary.
Add these to your project.clj :dependencies. First up a private fork of clairvoyant.
Then the customised tracer for cljs-devtools that includes a colour choice
Next, we're going to assume that you have structured you app in the recomended way
You'll need to make changes to handlers.cljs, subs.cljs and views.cljs. These namespaces contain the functions we want to trace.
-
At the top of each add these requires:
[clairvoyant.core :refer-macros [trace-forms]] [re-frame-tracer.core :refer [tracer]]
-
Then, immediately after the
nsform add (if you want a green colour):(trace-forms {:tracer (tracer :color "green")}
-
Finally, put in a closing
)at the end of the file. -
Colour choice
We have sauntered in the direction of the following colours
file colour handlers.cljgreen subs.cljsbrown views.cljgold But I still think orange, flared pants are a good look. So, yeah. You may end up choosing others.
-
Subscriptions issues
Unfortunately since both
trace-formsandreactionare macros they don't work well together. So there is some necessary changes to your subscriptions code to get them to work with clairvoyant, you need to replace the macroreactionwith the functionmake-reaction.so the following code
(ns my.ns (:require-macros [reagent.ratom :refer [reaction]])) ;; ... (subs/register :my-sub (fn [db _] (reaction (get-in @db [db-root :my-sub]))))
needs to become
(ns my.ns (:require [reagent.ratom :refer [make-reaction]])) ;; ... (subs/register :my-sub (fn [db _] (make-reaction (fn my-subscription [] (get-in @db [db-root :my-sub])))))
To get good quality tracing, you need to provide names for all your functions. Don't let functions be anonymous.
For example, make sure you name the renderer in a Form2 component:
(defn my-view
[]
(let [name (subscribe [:name])]
(fn my-view-rendere [] ;; <-- name it!!
[:div @name])))And name those event handlers:
(register-handler
:blah
[middlewares]
(fn blah-handler [db v] ;; <-- name it
(reaction (:blah @db))))It would be annoying if the clairvoyant code had to be removed for production. ... it doesn't have to be ...
Do this in your project.clj file:
:cljsbuild {:builds [{:id "production"
....
:closure-defines {:goog.DEBUG false}
:optimizations :advanced ;; simple will not remove the tracing code
}]}That will remove all the clairvoyant tracing code from your production code.
Load your app, and open the dev-tools console. Make an event happen (click a button?). Notice the colour coded tracing showing the functions being called and the derived data flowing.
Do you see the dominos?
XXX Point out Debugging Event Handlers page
XXX talk about [:pre ]
XXX beware very large props (parameters) ... will cause Chrome to use a lot of memory in devtools.
Deprecated Tutorials:
Reagent: