Talking To Servers
Talking To Servers¶
This page describes how a re-frame app might "talk" to a backend HTTP server.
We'll assume there's a json-returning server endpoint
at "http://json.my-endpoint.com/blah". We want to GET from that
endpoint and put a processed version of the returned json into app-db
.
Triggering The Request¶
The user often does something to trigger the process.
Here's a button which the user could click:
(defn request-it-button
[]
[:div {:class "button-class"
:on-click #(dispatch [:request-it])} ;; get data from the server !!
"I want it, now!"])
Notice the on-click
handler - it dispatch
es the event [:request-it]
.
The Event Handler¶
That :request-it
event will need to be "handled", which means an event handler must be registered for it.
We want this handler to:
1. Initiate the HTTP GET
2. Update a flag in app-db
which will trigger a modal "Loading ..." message for the user to see
We're going to create two versions of this event handler. First, we'll create a problematic version of the event handler and then, realising our sins, we'll write a second version which is a soaring paragon of virtue. Both versions will teach us something.
Version 1¶
We're going to use the cljs-ajax library as the HTTP workhorse.
Here's the event handler:
(ns my.app.events ;; <1>
(:require [ajax.core :refer [GET]]
[re-frame.core :refer [reg-event-db]))
(reg-event-db ;; <-- register an event handler
:request-it ;; <-- the event id
(fn ;; <-- the handler function
[db _]
;; kick off the GET, making sure to supply a callback for success and failure
(GET
"http://json.my-endpoint.com/blah"
{:handler #(dispatch [:process-response %1]) ;; <2> further dispatch !!
:error-handler #(dispatch [:bad-response %1])}) ;; <2> further dispatch !!
;; update a flag in `app-db` ... presumably to cause a "Loading..." UI
(assoc db :loading? true))) ;; <3> return an updated db
Further Notes:
1. Event handlers are normally put into an events.cljs
namespace
2. Notice that the GET callbacks issue a further dispatch
. Such callbacks
should never attempt to close over db
themselves, or make
any changes to it because, by the time these callbacks happen, the value
in app-db
may have changed. Whereas, if they dispatch
, then the event
handlers looking after the event they dispatch will be given the latest copy of the db.
3. event handlers registered using reg-event-db
must return a new value for
app-db
. In our case, we set a flag which will presumably cause a "Loading ..."
UI to show.
Successful GET¶
As we noted above, the on-success handler itself is just
#(dispatch [:process-response RESPONSE])
. So we'll need to register a handler
for this event too.
Like this:
(reg-event-db
:process-response
(fn
[db [_ response]] ;; destructure the response from the event vector
(-> db
(assoc :loading? false) ;; take away that "Loading ..." UI
(assoc :data (js->clj response)))) ;; fairly lame processing
A normal handler would have more complex processing of the response. But we're just sketching here, so we've left it easy.
There'd also need to be a handler for the :bad-response
event too. Left as an exercise.
Problems In Paradise?¶
This approach will work, and it is useful to take time to understand why it would work, but it has a problem: the event handler isn't pure.
That GET
is a side effect, and side effecting functions are like a
well salted paper cut. We try hard to avoid them.
Version 2¶
The better solution is, of course, to use an effectful handler. This is explained in detail in the previous tutorials: Effectful Handlers and Effects.
In the 2nd version, we use the alternative registration function, reg-event-fx
, and we'll use an
"Effect Handler" supplied by this library
https://github.com/day8/re-frame-http-fx.
You may soon feel confident enough to write your own.
Here's our rewrite:
(ns my.app.events
(:require
[ajax.core :as ajax]
[day8.re-frame.http-fx]
[re-frame.core :refer [reg-event-fx]))
(reg-event-fx ;; <-- note the `-fx` extension
:request-it ;; <-- the event id
(fn ;; <-- the handler function
[{db :db} _] ;; <-- 1st argument is coeffect, from which we extract db
;; we return a map of (side) effects
{:http-xhrio {:method :get
:uri "http://json.my-endpoint.com/blah"
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})
:on-success [:process-response]
:on-failure [:bad-response]}
:db (assoc db :loading? true)}))
Notes:
1. Our event handler "describes" side effects, it does not "do" side effects
2. The event handler we wrote for :process-response
stays as it was