HTTP Effects Handler For re-frame
This re-frame library contains an HTTP Effect Handler.
Keyed :http-xhrio
, it wraps the goog xhrio API of cljs-ajax.
IMPORTANT: This effect handler depends entirely on the API of cljs-ajax. Make sure you are familiar with the API for cljs-ajax, and especially with
ajax-request
before proceeding.
Quick Start Guide
Step 1. Add Dependency
Add the following project dependency:
Requires re-frame >= 0.8.0
Step 2. Registration And Use
In the namespace where you register your event handlers, perhaps called events.cljs
, you have 2 things to do.
First, add this "require" to the ns
:
(ns app.core
(:require
...
[day8.re-frame.http-fx] ;; <-- add this
...))
Because we never subsequently use this require
, it
appears redundant. But its existence will cause the :http-xhrio
effect
handler to self-register with re-frame, which is important
to everything that follows.
Second, write a an event handler which uses this effect:
(ns app.events ;; or where ever you define your event handlers
(:require
...
[ajax.core :as ajax] ;; so you can use this in the response-format below
...))
(reg-event-fx ;; note the trailing -fx
:handler-with-http ;; usage: (dispatch [:handler-with-http])
(fn [{:keys [db]} _] ;; the first param will be "world"
{:db (assoc db :show-twirly true) ;; causes the twirly-waiting-dialog to show??
:http-xhrio {:method :get
:uri "https://api.github.com/orgs/day8"
:timeout 8000 ;; optional see API docs
:response-format (ajax/json-response-format {:keywords? true}) ;; IMPORTANT!: You must provide this.
:on-success [:good-http-result]
:on-failure [:bad-http-result]}}))
Look at the :http-xhrio
line above. This library defines the "effects handler"
which implements :http-xhrio
.
The supplied value should be an options map as defined by the simple interface ajax-request
see: api docs. Except for :on-success
and :on-failure
. All options supported by ajax-request
should be supported by this library, as it is a thin wrapper over ajax-request
.
Here is an example of a POST request. Note that :format
also needs to be specified (unless you pass :body
in the
map).
(re-frame/reg-event-fx
::http-post
(fn [_world [_ val]]
{:http-xhrio {:method :post
:uri "https://httpbin.org/post"
:params data
:timeout 5000
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::success-post-result]
:on-failure [::failure-post-result]}}))
N.B.: ajax-request
is harder to use than the GET
and POST
functions
cljs-ajax provides, but this gives you smaller code sizes from dead code elimination.
In particular, you MUST provide a :response-format
, it is not inferred for you.
Don't provide:
:api - the effects handler explicitly uses xhrio so it will be ignored.
:handler - we substitute this with one that dispatches `:on-success` or `:on-failure` events.
You can also pass a list or vector of these options maps where multiple HTTPs are required.
To make multiple requests, supply a vector of options maps:
{:http-xhrio [ {...}
{...}]}
:on-success
Step 3a. Handling Provide normal re-frame handlers for :on-success
and :on-failure
. Your event
handlers will get the result as the last argument of their event vector. Here is an
example written as another effect handler to put the result into db.
(reg-event-db
::success-http-result
(fn [db [_ result]]
(assoc db :success-http-result result)))
:on-failure
Step 3b. Handling The result
supplied to your :on-failure
handler will be a map containing various xhrio details (details below).
See the fn ajax-xhrio-handler for details
Step 3.1 :on-failure result
A simple failure handler could be written this way ...
(reg-event-db
::failure-http-result
(fn [db [_ result]]
;; result is a map containing details of the failure
(assoc db :failure-http-result result)))
status of 40x/50x
If the network connection to the server is successful, but the server returns an
error (40x/50x) HTTP status code result
will be a map like:
{:uri "/error"
:last-method "GET"
:last-error "Service Unavailable [503]"
:last-error-code 6
:debug-message "Http response at 400 or 500 level"
:status 503
:status-text "Service Unavailable"
:failure :error
:response nil}
Status 0
In some cases, if the network connection itself is unsuccessful, it is possible
to get a status code of 0
. For example:
- cross-site scripting whereby access is denied; or
- requesting a URI that is unreachable (typo, DNS issues, invalid hostname etc); or
- request is interrupted after being sent (browser refresh or navigates away from the page); or
- request is otherwise intercepted (check your ad blocker).
In this case, result
will be something like:
{:uri "http://i-do-not-exist/error"
:last-method "GET"
:last-error " [0]"
:last-error-code 6
:debug-message "Http response at 400 or 500 level"
:status 0
:status-text "Request failed."
:failure :failed}
Status -1
If the time for the sever to respond exceeds :timeout
result
will be a map something
like:
{:uri "/timeout"
:last-method "GET"
:last-error "Timed out after 1ms, aborting"
:last-error-code 8
:debug-message "Request timed out"
:status -1
:status-text "Request timed out."
:failure :timeout}
Optional: Handler for :on-request
If you need access to the raw request, to for example, cancel long running requests or repeated debounced requests,
you can pass an :on-request
handler that will be called with the request.
(re-frame/reg-event-fx
::http-post
(fn [_world [_ val]]
{:http-xhrio {:method :get
:uri "https://httpbin.org/delay/60"
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})
:on-request [::track-slow-request "my-request"]
:on-success [::success-get-result]
:on-failure [::failure-get-result]}}))
(reg-event-db
::track-slow-request
(fn [db [_ my-id xhrio]]
(assoc-in db [:requests my-id] xhrio)))
Later if you need to, you could retrieve the request from the app-db and cancel it.
N.B.: To prevent memory leaks you need to cleanup the request in both your :on-success
and :on-failure
handlers.
Otherwise the requests will just hang around in your app-db indefinitely.
Tip
If you need additional arguments or identifying tokens in your handler, then
include them in your :on-success
and :on-failure
event vector in Step 3.
For example ...
(re-frame/reg-event-fx
::http-post
(fn [_ [_ val]]
{:http-xhrio {:method :post
...
:on-success [::success-post-result 42 "other"]
:on-failure [::failure-post-result :something :else]}}))
Notice the way that additional values are encoded into the success and failure event vectors.
These event vectors will be dispatched (result
is conj
-ed to the end) making all encoded values AND the result
available to the handlers.