re-frame/docs/FAQs/PollADatabaseEvery60.md

116 lines
4.9 KiB
Markdown
Raw Normal View History

2017-08-28 17:13:19 +10:00
### Question
2017-09-01 15:22:45 +10:00
When the user switches to a certain panel, I'd like to start regularly polling my
2017-09-01 11:52:23 +10:00
backend (database) - say every 60 seconds. And, then later, when the user switches
away from that panel, I want to stop that polling.
2017-08-28 17:13:19 +10:00
2017-09-01 11:08:39 +10:00
### First, An Architectural Note
2017-08-28 17:13:19 +10:00
2017-09-01 15:22:45 +10:00
The broader React community sometimes likes to collocate queries with view components
2017-09-01 15:35:37 +10:00
and initiate those queries (via a GET?) within the View's `componentDidMount`.
2017-09-01 15:22:45 +10:00
And then, perhaps cleanup/stop any database polling in the Component's `componentWillUnmount`.
2017-08-28 17:13:19 +10:00
2017-09-01 15:35:37 +10:00
This approach is not idiomatic for re-frame. Views are not imperative
(they don't issue database queries), they simply render current application state.
[Read more in PurelyFunctional.tv's writeup](https://purelyfunctional.tv/article/react-vs-re-frame/)
2017-08-28 17:13:19 +10:00
2017-09-01 15:22:45 +10:00
With re-frame, "imperative stuff" only ever happens
2017-09-01 11:08:39 +10:00
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
2017-09-01 15:22:45 +10:00
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
2017-09-01 11:08:39 +10:00
"twirly thing" is shown over the top, and Z might be to issue a database query.
2017-08-28 17:13:19 +10:00
2017-09-01 15:22:45 +10:00
So, having got that issue out the way ...
2017-08-28 17:13:19 +10:00
2017-09-01 11:08:39 +10:00
### Answer
2017-08-28 17:13:19 +10:00
2017-09-01 11:08:39 +10:00
We'll create an effect. It will be general in nature.
2017-08-28 17:13:19 +10:00
2017-09-01 15:22:45 +10:00
It will start and stop the timed/scheduled dispatch of an event.
2017-09-01 11:52:23 +10:00
For this FAQ,
2017-09-01 15:35:37 +10:00
we want an event dispatched every 60 seconds and each event will
2017-09-01 15:22:45 +10:00
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`. So, event handlers
will be returning:
2017-09-01 15:35:37 +10:00
```clj
{:interval <something>}
2017-09-01 15:22:45 +10:00
```
2017-09-01 15:35:37 +10:00
So now we design the `<something>` bit. It will be a data format (DSL) which
2017-09-01 15:22:45 +10:00
allows an event handler to start and stop a regular event dispatch.
2017-09-01 11:08:39 +10:00
To `:start` a regular dispatch, an event handler would return
data in this format:
2017-08-28 17:13:19 +10:00
```clj
{:interval {:action :start
2017-09-01 15:22:45 +10:00
:id :panel-1-query ;; my id for this (so I can cancel later)
2017-09-01 11:52:23 +10:00
:frequency 60000 ;; how many ms between dispatches
2017-08-28 17:13:19 +10:00
:event [:panel-query 1]}} ;; what to dispatch
```
2017-09-01 11:08:39 +10:00
And to later cancel the regular dispatch, an event handler would return this:
2017-08-28 17:13:19 +10:00
```clj
{:interval {:action :cancel
2017-09-01 11:52:23 +10:00
:id :panel-1-query}} ;; the id provided to :start
2017-08-28 17:13:19 +10:00
```
2017-09-01 15:35:37 +10:00
With that design work done, let's now implement it by registering an
`effect handler`:
2017-08-28 17:13:19 +10:00
```clj
2017-09-01 11:52:23 +10:00
(re-frame.core/reg-fx ;; the re-frame API for registering effect handlers
:interval ;; the effect id
2017-09-01 15:35:37 +10:00
(let [live-intervals (atom {})] ;; storage for live intervals
2017-09-01 11:52:23 +10:00
(fn [{:keys [action id frequency event]}] ;; the handler
2017-08-28 17:13:19 +10:00
(if (= action :start)
(swap! live-intervals assoc id (js/setInterval #(dispatch event) frequency)))
2017-10-10 23:19:55 +11:00
(do (js/clearInterval (get @live-intervals id))
2017-08-28 17:13:19 +10:00
(swap! live-intervals dissoc id))))
```
You'd probably want a bit more error checking, but that's the (untested) sketch.
2017-09-01 15:22:45 +10:00
### A Side Note About Effect Handlers and Figwheel
2017-09-01 15:35:37 +10:00
[Figwheel](https://github.com/bhauman/lein-figwheel) provides for the hot reloading of code, which
2017-09-01 15:22:45 +10:00
is terrific.
But, during development, as Figwheel is reloading code, effectful handlers, like the
2017-09-01 15:35:37 +10:00
one above, can be get into a messed up state - existing timers might be lost (and
become never-stoppable).
2017-09-01 15:22:45 +10:00
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.
2017-09-01 15:35:37 +10:00
One strategy is to put all your grubby effect handlers into their own
separate namespace `effects.cljs` - one that isn't edited often, removing
the trigger for a Figwheel reload.
2017-09-01 15:22:45 +10:00
OR, you can code defensively for reloading, perhaps like this:
```clj
2017-09-01 15:35:37 +10:00
(defonce interval-handler ;; notice the use of defonce
(let [live-intervals (atom {})] ;; storage for live intervals
2017-09-01 15:22:45 +10:00
(fn handler [{:keys [action id frequency event]}] ;; the effect handler
(condp = action
2017-09-01 15:35:37 +10:00
:clean (doseq ;; <--- new. clean up all existing
2017-09-01 15:22:45 +10:00
#(handler {:action :end :id %1})
(keys @live-intervals))
:start (swap! live-intervals assoc id (js/setInterval #(dispatch event) frequency)))
2017-10-10 23:19:55 +11:00
:end (do (js/clearInterval (get @live-intervals id))
2017-09-01 15:22:45 +10:00
(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)
```
2017-09-01 15:35:37 +10:00
**Key takeaway:** every effect handler is statefully 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. Sometimes
there's no escaping an application restart.
2017-08-28 17:13:19 +10:00