re-frame/docs/FAQs/PollADatabaseEvery60.md

123 lines
5.1 KiB
Markdown
Raw Normal View History

2017-08-28 17:13:19 +10:00
### Question
2017-10-10 23:58:45 +11:00
When the user switches to a particular 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-10-10 23:58:45 +11:00
The broader React community often uses a "load data on mount" approach.
They collocate queries with view components
and initiate these queries (via a GET?) within the View's `componentDidMount` lifecycle method.
And then, later, they might cleanup/stop any database polling in `componentWillUnmount`.
2017-08-28 17:13:19 +10:00
2017-10-10 23:58:45 +11:00
This arrangement is not idiomatic for re-frame. Views are not imperative and
they don't initiate database queries. Instead, views simply render current application state.
2017-09-01 15:35:37 +10:00
[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-10-10 23:58:45 +11:00
because an `event` is dispatched. When the user clicks on a panel-changing widget
(perhaps a button or a tab?),
an event is dispatched, and it is the event handler associated with this event which
computes the effects of the user's action. First, it might change application state so
the panel is shown, and then it might further change application state so that a
"twirly busy" thing is shown and, finally, it might 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-10-10 23:58:45 +11:00
### An 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
***
Up: [FAQ Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;