-
-
Notifications
You must be signed in to change notification settings - Fork 720
Bootstrap An Application
To bootstrap a reframe application, you need to:
- register handlers (subscription and event handlers)
- kickstart reagent (views)
- get the right data into
app-db
which might be amerge
of:
- Some default values
- Values stored in LocalStorage
- Values obtained via service calls to server
Point 3 is the interesting bit and will be the main focus of this page, but let's work our way through them ...
Generally, there's nothing to do because this happens automatically at (js) script load time, because you declared and registered your handlers like this:
(register-pure-handler ;; handler will be registered automatically
:some-id
(fn [db v]
.. do some state change based on db and v)
Create a function main
which does a reagent/render
of your root reagent component main-panel
:
(defn main-panel
[]
[:div "Hello DDATWD"])
(defn ^:export main ;; call this to bootstrap your app
[]
(reagent/render [main-panel]
(js/document.getElementById "app")))
Imagine our component main-panel
uses a subscription:
(register-sub
:name ;; usage (subscribe [:name])
(fn [db]
(reaction (:name @db)))
(defn main-panel
[]
(let [name (subscribe :name)] ;; <--- a subscription <---
(fn []
[:div "Hello " @name])))) ;; <--- use the result of the subscription
The user will see funny things
if that (subscribe [:name])
doesn't deliver good data. We have to ensure there is good data in app-db
.
That will require:
- getting data into
app-db
; and - not get into trouble if that data isn't yet in
app-db
? For example, the data may have to come from a server and there's latency.
Note: app-db
initially contains {}
Only event handlers can change app-db
. Those are the rules!! Even if we want to put in initial values, it has to be by via an event handler.
Here's an event handler for that purpose:
(register-pure-handler
:initialise-db ;; usage: (dispatch [:initialise-db])
(fn
[_ _] ;; Ignore both params (db and v). return a new value for app-db
{:initialised true
:display-name "DDATWD"
:items [1 2 3 4]}))
We'll need a dispatch
:initialise-db
to get that event handler executed. main
seems like the natural place:
(defn ^:export main
[]
(dispatch [:initialise-db]) ;; <--- this is new
(reagent/render [name-panel]
(js/document.getElementById "app")))
But remember, event handlers execute async. So although there's a dispatch
within main
, the handler for :initialise-db
will not be run until sometime after main
has finished.
But how long after? And is there a race condition? The component main-panel
(which needs good data) might be rendered by reagent before the :initialise-db
event handler has put the default data into app-db
.
We want to stop any rendering until after app-db
is initialised.
Okay, so that's enough of teasing-out the issues. Let's see a quick micro sketch of the entire pattern. Its quite straight-forward:
(register-sub
:name ;; usage (subscribe [:name])
(fn [db]
(reaction (:name @db))))
(register-sub
:initialised? ;; usage (subscribe [:initialised?])
(fn [db]
(reaction (not (empty? @db)))) ;; do we have data
(defn main-panel ;; real content for the user to see
[]
(let [name (subscript :name)]
(fn []
[:div "Hello " @name]))))
(defn top-panel
[]
(let [ready? (subscribe :initialised?)]
(fn []
(if-not @ready? ;; don't even render main-panel until we have data
[:div "Initialising ..."]
[main-panel]))))
(defn ^:export main ;; call this to bootstrap your app
[]
(dispatch [:initialise-db])
(reagent/render [top-panel]
(js/document.getElementById "app")))
This pattern scales up easily.
For example, imagine a more complicated scenario in which your app is not fully initialised until 2 backend services supply data.
Your main
might look like this:
(defn ^:export main ;; call this to bootstrap your app
[]
(dispatch [:initialise-db]) ;; basics
(dispatch [:load-from-service-1]) ;; ask for data from service-1
(dispatch [:load-from-service-2]) ;; ask for data from service-2
(reagent/render [top-panel]
(js/document.getElementById "app")))
Your :initialised?
test then becomes more like this sketch:
(register-sub
:initialised? ;; usage (subscribe [:initialised?])
(fn [db]
(reaction (and (not (empty? @db))
(:service1-answered? @db)
(:service2-answered? @db)))))
This assumes boolean flags are set in app-db
when data was loaded from these services.
And what would a sketch of a service request look like:
(register-pure-handler
:process-service-1-response
(fn
[db [_ response]]
(-> db
(assoc :service1-answered? true)
(assoc :service1-data response)))) ;; fairly lame processing of the response
(register-pure-handler
:load-from-service-1
(fn
[db _]
(ajax.core/GET
"/some/url"
{:handler #(dispatch [:process-service-1-response %1])
:error-handler #(dispatch [:bad-response %1])})))
Deprecated Tutorials:
Reagent: