Final cut of FAQ

This commit is contained in:
Mike Thompson 2017-09-01 15:22:45 +10:00
parent 0274c784b6
commit f4b9fa0d65
1 changed files with 65 additions and 22 deletions

View File

@ -1,50 +1,52 @@
### Question
When the user switches to a certain panel, I'd like to kickoff a regular poll of my
When the user switches to a certain panel, I'd like to start regularly polling my
backend (database) - say every 60 seconds. And, then later, when the user switches
away from that panel, I want to stop that polling.
### First, An Architectural Note
The broader React community often uses the "load data from server on Component mount"
model of architecture. They'll collocate queries with view components and perform a
GET (to the server) within the View's `componentDidMount`.
The broader React community sometimes likes to collocate queries with view components
and initiate those queries (via a GET?) within the View's `componentDidMount`.
And then, perhaps cleanup/stop any database polling in the Component's `componentWillUnmount`.
This approach is not idiomatic for re-frame. Views are not imperative in nature
This approach is not idiomatic for re-frame. Views are not imperative
(they don't issue database queries), they simply render current application state.
This and more
[is discussed in PurelyFunctional.tv's writeup](https://purelyfunctional.tv/article/react-vs-re-frame/)
re-frame is CQRS in nature - "imperative stuff" only ever happens
With re-frame, "imperative stuff" only ever happens
because of an `event`. When the user clicks on a panel-changing widget (a button or a tab?),
an event is dispatched, and it is the event handler for this event which knows
that action X, Y and Z needs to happen. X might be change application state so
that the view displayed changes, Y might be change application state so that a
that actions X, Y and Z needs to happen. X might be change application state so
that the view displayed changes, and Y might be change application state so that a
"twirly thing" is shown over the top, and Z might be to issue a database query.
So, having got that out the way ...
So, having got that issue out the way ...
### Answer
We'll create an effect. It will be general in nature.
It will start and stop the timed/scheduled dispatch of an event (every 60 seconds).
It will start and stop the timed/scheduled dispatch of an event.
For this FAQ,
this event will poll the backend but this is a general pattern
and the regularly dispatched event could do anything we wanted.
we want an event dispatched every 60 seconds and each event will
trigger a backend poll, but the effect we are about to create
will be useful well beyond this narrow case.
We'll be creating an `effect` called, say, `:interval`. We must
design the data format (micro DSL) returned by an
event handler for this effect. This data format must allow an event handler to
start and stop a regular dispatch.
We'll be creating an `effect` called, say, `:interval`. So, event handlers
will be returning:
```
{:interval something}
```
So now we design the "something" bit. It will be a data format (DSL) which
allows an event handler to start and stop a regular event dispatch.
To `:start` a regular dispatch, an event handler would return
data in this format:
```clj
{:interval {:action :start
:id :panel-1-query ;; my id for this (so I cancel later)
:id :panel-1-query ;; my id for this (so I can cancel later)
:frequency 60000 ;; how many ms between dispatches
:event [:panel-query 1]}} ;; what to dispatch
```
@ -55,7 +57,8 @@ And to later cancel the regular dispatch, an event handler would return this:
:id :panel-1-query}} ;; the id provided to :start
```
Now, register an `effect handler` for the `:interval` effect:
So that's the design work done. Let's implement it by registering an
`effect handler` for the `:interval` effect:
```clj
(re-frame.core/reg-fx ;; the re-frame API for registering effect handlers
:interval ;; the effect id
@ -69,6 +72,46 @@ Now, register an `effect handler` for the `:interval` effect:
You'd probably want a bit more error checking, but that's the (untested) sketch.
### Figwheel
### A Side Note About Effect Handlers and Figwheel
[Figwheel](https://github.com/bhauman/lein-figwheel) provides for hot reloading of code, which
is terrific.
But, during development, as Figwheel is reloading code, effectful handlers, like the
one above, can be get into a messed up state - existing timers will be lost (and
become never stoppable).
Stateful things are grubby in the face of reloading, and all we can do is
try to manage for it as best we can, on a case by case basis.
You could try putting all your grubby effect handlers into their own
separate namespace `effects.cljs` - one that isn't edited often, so Figwheel isn't
regularly reloading it.
OR, you can code defensively for reloading, perhaps like this:
```clj
(defonce interval-handler ;; notice use of defonce
(let [live-intervals (atom {})] ;; storage for live interevals
(fn handler [{:keys [action id frequency event]}] ;; the effect handler
(condp = action
:clean (doseq ;; clean up all existing
#(handler {:action :end :id %1})
(keys @live-intervals))
:start (swap! live-intervals assoc id (js/setInterval #(dispatch event) frequency)))
:end (do (js/clearInterval (get live-intervals id))
(swap! live-intervals dissoc id))))
;; when this code is reloaded `:clean` existing intervals
(interval-handler {:action :clean})
;; now register
(re-frame.core/reg-fx ;; the re-frame API for registering effect handlers
:interval ;; the effect id
interval-handler)
```
Every effect handler is grubby in its own special way. So you'll have to
come up with strategies to handle Figwheel reloads on a case by case basis.
Figwheel is a really